Иллюстрированный самоучитель по Visual Studio .NET

Обращаемся к операционной системе

Внутри многих классов MFC скрыто существуют Windows-описатели, которые должны быть правильно инициализированы. Часто, но не всегда, это делается без нашего участия. Иногда мы должны предпринять какие-то действия для инициализации описателя. В данном случае это можно сделать прямым присвоением, например m_pimgList › m_hImageList = himg; но такой способ менее надежен, так как в нем непосредственно запоминается какой-то адрес памяти. Содержимое по этому адресу система может изменить в результате наших же манипуляций с объектами, и тогда мы получим проблему под названием "Irreproducible Bug" (невоспроизводимая ошибка). Точнее будет сказать трудновоспроизводимая ошибка – самый неприятный тип ошибок, для борьбы с которыми идут в ход все средства (даже AssertValid и Dump).

Значительно надежнее использовать метод Attach класса CimageList, так как в этом случае система будет следить за перемещениями структур, адресуемых описателем. При этом работает класс CHandleMap и его метод SetPermanent, которые, к сожалению, не документированы.

Связывание списка с объектом m_Tree производит функция SetlmageList, последний параметр которой (TVSIL_NORMAL) говорит о том, что тип списка обычный, то есть состоит из двух изображений. Альтернативным выбором является TVSIL_STATE, справку о нем вы получите самостоятельно, если захотите. Поместите следующий код в файл LeftView.cpp. Он вставляет в дерево новый элемент с изображением, которое ему соответствует:

void CLeftView::AddItem (HTREEITEM h, LPCTSTR s)
{
SHFILEINFO Info;
int len = sizeof (Info);
//=== Добываем изображение (маленький значок)
::SHGetFileInfo (s, 0, SInfo, len, SHGFI_ICON
| SHGFI_SMALLICON); int id = Info.ilcon;
//=== Добываем изображение в выбранном состоянии
::SHGetFileInfo (s,0,Slnfo,len,
SHGFI_ICON | SHGFI_OPENICON | SHGFI_SMALLICON);
int idSel = Info.ilcon;
//====== Копируем параметр в рабочую строку
CString sName(s);
//=== Отсекаем лишние символы (сначала в конце строки)
if (sName.Right(1) == '\\')
sName.SetAt (sName.GetLength() -1, '\0');
//====== Затем в начале строки
int iPos = sNarae.ReverseFind('\\');
if (iPos!= -1)
sName = sNarne.Mid(iPos + 1);
//=== Вставляем узел в дерево
HTREEITEM hNew = m_Tree.InsertltemfsName,id,idSel,h);
//====== Вставляем пустой узел
if (NotErapty(s))
m_Tree.Insertltem("", 0, 0, hNew);
}

Функция SHGetFilelnfо вызывается дважды, так как от системы надо получить два индекса изображений: для объекта файловой системы в обычном состоянии и для него же в выбранном состоянии. Метод Insertltem класса CTreeCTRL вставляет узел в дерево. Его параметры задают:

  • местоположение узла, то есть описатель родительского узла (h),
  • соответствующий узлу дерева текст (s),
  • индексы двух изображений (id, idSel) в уже сформированном списке типа CImageList.

Вставляемый в дерево логический диск надо проверить на наличие вложенных сущностей и вставить внутрь данного узла дерева хотя бы один элемент, когда диск не пуст. Если этого не сделать, то в дереве не будет присутствовать маркер (+), с помощью которого пользователь раскрывает узел.

При проверке диска (функция NotEmpty) мы не сканируем его далеко вглубь, а просто проверяем на наличие хотя бы одной папки. Если диск имеет хотя бы одну папку, то вставляем внутрь соответствующего ей узла пустой элемент (Insertltem ("", 0, 0, h)), который дает возможность впоследствии раскрыть (expand) данный узел. Затем, когда пользователь действительно его раскроет, мы обработаем это событие и удалим пустой элемент. Вместо него наполним раскрытую ветвь реальными сущностями. Этот прием обеспечивает постепенное наполнение дерева по сценарию, определяемому действиями пользователя.

Примечание
Сначала я написал рекурсивную функцию анализа и заполнения всего файлового дерева при начальном запуске приложения. Оказалось, что эта процедура занимает 5-7 минут, в течение которых приложение выглядит мертвым. Правда, после нее дерево раскрывает свои ветви мгновенно, так как оно уже хранит информацию обо всех своих ветвях. В выбранном варианте работы с деревом вновь раскрываемые ветви вносят некоторую задержку, но после схлопывания (collapse) какой-либо ветви ее повторное раскрытие происходит быстро, так как информация уже имеется в дереве, точнее в элементе CTreeCTRL Другим вариантом решения проблемы является параллельное сканирование файлового дерева в другом потоке приложения
.

Операция отсечения лишних символов нам понадобилась для того, чтобы из длинного файлового пути выделить только имя папки, которое должно появится в дереве справа от bitmap-изображения объекта – узла дерева. Мы решили показывать в дереве, в левом окне приложения, только папки. Файлы этих папок будут изображены в виде картинок в другом, правом, окне. Картинкой я называю содержимое документа в виде его чертежа – многоугольника (для простоты). Показывать будем только те файлы, которые соответствуют документам нашего приложения. Если вы помните, они должны иметь расширение mgn, как это было определено на этапе работы с мастером AppWizard.

При усечении строки необходимо использовать знание структуры файлового пути и методы класса cstring. Сначала отсекаем символ ' \' справа от имени папки, затем все символы слева от него. Существует и другой способ, использующий функцию _splitpath, справку по которой я рекомендую получить самостоятельно. В настоящий момент развития приложения строка sName может содержать только одно из имен логических дисков и большая часть кода работает вхолостую, но чуть позже, когда мы будем иметь дело с длинными файловыми путями, он заработает полностью.

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.