Валерий Борисок - Delphi. Трюки и эффекты
Установка состояния элемента дерева
procedure TForm3.SetExpanded(Node: TTreeNode; isExpanded: Boolean);
begin
if isExpanded then
begin
//Подготавливаем элемент к загрузке содержимого каталога
Node.Data := Pointer(1);
Node.DeleteChildren;
end
else
begin
//Содержимое каталога не прочитано (или его следует обновить)
Node.Data := Pointer(0);
Node.Collapse(False);
Node.DeleteChildren;
tree.Items.AddChild(Node, ''); //Фиктивный элемент(чтобы
//отображался "+", позволяющий
//развернуть элемент)
end;
end;
Если после создания элементов дерева процедура SetExpanded вызывается с параметром isExpanded, равным False (как в листинге 4.27), то для переданного в процедуру элемента дерева создается фиктивный дочерний элемент. Это делается для того, чтобы не зачитывать содержимое каждого не развернутого еще элемента дерева (для папок с большим количество файлов программа будет сильно «тормозить»). А так у каждого еще не развернутого элемента отображается символ +, позволяющий развернуть его в нужный момент. При этом не нужно забывать удалять созданный фиктивный элемент дерева (что и делает SetExpanded с параметром isExpanded, равным True).
Каждый не развернутый еще элемент дерева помечается значением поля Node. Data, равным 0. Каждый элемент, содержимое которого уже прочитано с диска, помечается значением поля Node. Data, равным 1. Для проверки, было ли прочитано содержимое каталога, соответствующего элементу дерева, используется простая функция IsExpanded (листинг 4.29).
...Листинг 4.29.
Проверка, загружено ли содержимое каталога
function TForm3.IsExpanded(Node: TTreeNode): Boolean;
begin
IsExpanded := Integer(Node.Data) = 1;
end;
Загрузка содержимого каталога и одновременное формирование дочерних элементов в дереве происходят при разворачивании элемента дерева (листинг 4.30).
...Листинг 4.30.
Загрузка содержимого каталога
procedure TForm3.treeExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
var
strFolder: String;
subfolders: TStrings;
i: Integer;
item: TTreeNode;
begin
if not IsExpanded(Node) then
//Содержимое каталога нужно зачитать
SetExpanded(Node, True)
else
begin
//Список подкаталогов для выделенного каталога
//был составлен ранее
AllowExpansion := True;
Exit;
end;
//Составление списка подкаталогов
strFolder := NodeToFolderPath( Node );
subfolders := TStringList.Create;
if SearchInFolder(strFolder, '*', FILE_ATTRIBUTE_DIRECTORY,
subfolders)
then begin
//Добавим в дерево элементы, соответствующие подкаталогам
for i := 0 to subfolders.Count – 1 do
begin
item := tree.Items.AddChild(Node, subfolders[i]);
item.ImageIndex := 1;
item.SelectedIndex := 2;
SetExpanded(item, False); //Содержимое подкаталога еще
//не прочитано
end;
AllowExpansion := True;
end
else
//В каталоге нет подкаталогов
AllowExpansion := False;
subfolders.Free;
end;
В листинге 4.30 для определения пути каталога, заданного элементом дерева, используется функция NodeToFolderPath. Реализуется она совсем несложно (листинг 4.31).
...Листинг 4.31.
Определение полного пути элемента дерева
function TForm3.NodeToFolderPath(Node: TTreeNode): String;
var
path: String;
item: TTreeNode;
begin
item := Node;
while item <> nil do
begin
if path <> '' then
path := item.Text + '\' + path
else
path := item.Text;
item := item.Parent;
end;
NodeToFolderPath := path;
end;
Приведенный здесь пример построения дерева может пригодиться при решении некоторых задач. Дополнительно же нужно сказать, что на вкладке Samples (Delphi 7) можно найти компоненты, прекрасно подходящие для построения пользовательского интерфейса приложений для просмотра содержимого не только физически существующих дисков: полное дерево каталогов SheLLTreeView (включая корневой элемент Рабочий стол и прочие виртуальные каталоги), список основных элементов системы каталогов (ShellComboBox), а также элемент управления для просмотра содержимого папки (SheLLListView).
4.3. Файлы
В завершение главы рассмотрим три несложных примера работы с файлами: копирование файла (с отображением хода копирования в ProgressBar), определение значков, ассоциированных с файлами, и извлечение значков из ЕХЕ– и DLL-файлов.
Красивое копирование файла
Казалось бы, что особенного в организации копирования большого файла с отображением процесса: читай файл порциями, записывай прочитанные данные в файл назначения, попутно показывая в ProgressBar или где-то еще отношение объема переписанной информации к размеру файла. Однако зачем такие сложности? Ведь у API-функции CopyFi 1е, осуществляющей простое копирование файла, есть расширенный вариант – функция CopyFileEx, в которую встроена поддержка отображения процесса копирования (и не только это). Вот прототип функции CopyFileEx:
...function CopyFileEx(lpExistingFileName, lpNewFileName: PChar;
lpProgressRoutine: TFNProgressRoutine; lpData: Pointer;
pbCancel: PBool; dwCopyFlags: DWORD): BOOL; stdcall;
Итак, кроме пути исходного и конечного файлов, а также флагов (последний параметр), функция принимает ряд дополнительных параметров: адрес функции обратного вызова (IpProgressRoutine), указатель на данные, передаваемые в функцию обратного вызова (lpData), а также адрес переменной типа BOOL (pbCancel), при установке значения которой в True копирование прерывается.
Пример использования функции CopyFileEx в программе приведен в листинге 4.32. Здесь подразумевается, что кнопка cmbCopy используется как для запуска, так и для остановки процесса копирования. Также на форме присутствуют следующие элементы управления:
• индикатор pbCopyProgress, диапазон значений которого от 0 до 100;
• текстовое поле txtFrom с именем копируемого файла;
• текстовое поле txtTo с именем файла назначения.
...Листинг 4.32.
Использование функции CopyFileEx
procedure TForm1.cmbCopyClick(Sender: TObject);
begin
if cmbCopy.Caption = 'Копировать' then
begin
//Запускаем копирование
progress := pbCopyProgress; //Настроен от 0 до 100 %
bCancelCopy := False;
cmbCopy.Caption := 'Отмена
if CopyFileEx(PAnsiChar(txtFrom.Text), PAnsiChar(txtTo.Text),
Addr(CopyProgressFunc), nil, Addr(bCancelCopy),
COPY_FILE_FAIL_IF_EXISTS) = False
then
MessageBox(Handle, 'Не удается скопировать файл',
'Копирование', MB_ICONEXCLAMATION);
end
else
begin
//Останавливаем процесс копирования
bCancelCopy := True;
cmbCopy.Caption := 'Копировать
end;
end;
Из листинга 4.32 можно увидеть, что в качестве значения последнего параметра функции CopyFileEx можно передавать константу COPY_FILE_FAIL_IF_EXISTS (функция вернет False, если файл назначения уже существует, и не будет осуществлять копирование).
На самом деле значение параметра dwCopyFlags функции CopyFileEx может быть комбинацией значений COPY_FILE_FAIL_IF_EXISTS И COPY_FILE_RES TARTABLE, то есть представляет собой битовый флаг. Последнее значение используется для того, чтобы в случае прерывания копирование файла можно было возобновить. Функция CopyFileEx в этом случае сохраняет в файле назначения информацию, достаточную для возобновления процесса копирования.
В листинге 4.32 изменяется переменная progress – глобальная переменная-ссылка на TProgressBar, которая используется в функции обратного вызова. Переменная bCancelCopy, адрес которой передается в функцию CopyFileEx, также объявлена глобальной (в пределах модуля).
Теперь, наконец, рассмотрим функцию обратного вызова, осуществляющую в нашем случае отображение хода копирования на индикаторе (листинг 4.33).
...Листинг 4.33.
Функция, показывающая ход копирования файла
function CopyProgressFunc( TotalFileSize: Int64;
TotalBytesTransferred: Int64;
StreamSize: Int64;
StreamBytesTransferred: Int64;
dwStreamNumber: DWORD;
dwCallbackReason: DWORD;
hSourceFile: THandle;
hDestinationFile: THandle;
lpData: Pointer): DWORD; stdcall;
begin
progress.Position := 100 * TotalBytesTransferred div
TotalFileSize;
Application.ProcessMessages; //Чтобы не «зависал»
//интерфейс приложения
CopyProgressFunc := PROGRESS_CONTINUE;
end;
Пусть вас не смущает большое количество параметров функцииСоруРгодгеззЕипс. Применять их все далеко не обязательно (но они должны быть объявлены), хотя ничего сложного здесь нет. В листинге 4.33 использование параметров реализовано наиболее простым (на наш взгляд) и очевидным образом: значения параметров TotalBytesTransferred и TotalFileSize применяются для определения доли скопированной информации.
В листинге 4.33 вызов метода ProcessMessages объекта Application используется потому, что функция CopyFileEx возвращает управление программе только после завершения (или прерывания) копирования. Иначе пришлось бы создавать для копирования отдельный поток, усложняя листинг и отвлекая вас от главной цели этого примера.
Теперь несколько слов о возвращаемых функцией CopyProgressFunc значениях (в нашем примере используется только одно из четырех доступных значений). Список целочисленных констант, значения которых может возвращать функция CopyProgressFunc, таков:
• PROGRESS_CONTINUE – продолжать процесс копирования;
• PROGRESS_CANCEL – отмена копирования;
• PROGRESS_STOP – остановка копирования (можно возобновить);
• PROGRESS_QUIET – при возврате этого значения система перестает вызывать функцию CopyProgressFunc.
Внешний вид формы при копировании большого файла приводится на рис. 4.9.
Рис. 4.9. Копирование большого файла
Только не нужно забывать останавливать копирование при закрытии приложения или в прочих экстренных ситуациях. Так, если не предусмотреть обработку события CloseQuery для формы (рис. 4.9), то закрыть ее в ходе копирования обычным способом не удастся. Зато после завершения копирования (или при нажатии кнопки Отмена) форма тут же исчезнет. Странное поведение, не правда ли? Вариант более-менее адекватной реакции на закрытие формы приводится в листинге 4.34.