Текст книги "Программирование в Delphi. Трюки и эффекты"
Автор книги: Александр Чиртик
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 9 (всего у книги 24 страниц)
Рассмотрим довольно интересный пример, основанный на использовании функции поиска SearchInFolder, – построение дерева папок для определенного диска (рис. 4.7).
Рис. 4.7. Дерево папок
Для простоты (и чтобы не отвлекать внимание от построения дерева) диск задается в программе жестко – при необходимости это можно легко исправить (как определять диски, вы уже знаете).
Рассмотрим работу приложения по порядку. Элемент управления TreeView на форме имеет имя tree. На рис. 4.8 показано содержимое списка изображений (ImageList), используемого деревом.
Рис. 4.8. Изображения для элементов дерева
Первый элемент дерева, соответствующий диску, создается при обработке события FormCreate (листинг 4.27).
Листинг 4.27. Создание первого элемента дерева – диска
procedure TForm3.FormCreate(Sender: TObject);
begin
//Корневой элемент дерева (диск)
root:= tree.Items.Add(tree.Items.GetFirstNode, 'C:');
root.ImageIndex:= 0;
root.SelectedIndex:= 0;
SetExpanded(root, False);
end;
Здесь и далее в примере глобальная в пределах модуля переменная root позволяет быстро получать доступ к корневому элементу дерева. Используемая в листинге 4.27 процедура применяется для установки состояния элементов дерева (листинг 4.28).
Листинг 4.28. Установка состояния элемента дерева
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;
SetExp anded(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, создающий элемент управления, используемый для просмотра содержимого папки.
Файлы
В завершение главы будут рассмотрены три несложных примера работы с файлами: копирование файла (с отображением хода копирования в ProgressBar), определение значков, ассоциированных с файлами, и извлечение значков из EXE– и DLL-файлов.
Красивое копирование файлаКазалось бы, что особенного в организации копирования большого файла с отображением процесса: читай файл порциями и записывай прочитанные данные в файл назначения, попутно отображая в ProgressBar или где-то еще отношение объема переписанной информации к размеру файла. Однако зачем такие сложности? Ведь у API-функции CopyFile, выполняющей простое копирование файла, есть и расширенный вариант – функция 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, диапазон значений которого составляет от О до 1ОО;
• текстовое поле txtFrom, содержащее имя копируемого файла;
• текстовое поле txtTo, содержащее имя файла назначения.
Листинг 4.32. Использование функции CopyFileEx
procedure TForm1.cmbCopyClick(Sender: TObject);
begin
if cmbCopy.Caption = 'Копировать' then
begin
//Запускаем копирование
progress:= pb CopyProgress; //Настроен от 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_RESTARTABLE, то есть представлять собой битовый флаг. Последнее значение используется для того, чтобы копирование файла можно было остановить и потом возобновить в случае прерывания процесса. В этом случае функция CopyFileEx сохраняет в файле назначения информацию, достаточную для возобновления процесса копирования.
В листинге 4.32 используется переменная progress – глобальная переменная-ссылка на TProgressBar, используемая в функции обратного вызова. Переменная bCancelCopy, адрес которой передается в функцию CopyFileEx, также объявлена глобальной (в пределах модуля).
Теперь, наконец, рассмотрим функцию обратного вызова, выполняющую в данном случае отображение хода копирования на индикаторе ProgressBar (листинг 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;
Пусть вас не смущает большое количество параметров функции CopyProgressFunc. Применять их все далеко не обязательно (но они должны быть объявлены), хотя ничего сложного здесь нет. В листинге 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.
Листинг 4.34. Прерывание копирования при закрытии формы
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
//Останавливаем процесс копирования
bCancelCopy:= True;
end;
Как вариант, можно запретить закрытие формы (установить параметр CanClose в значение False), не прерывая копирования.
Если копируется несколько файлов, можно ввести дополнительный элемент управления ProgressBar, отображающий ход всего процесса копирования. Только при этом придется заранее определить общий размер копируемых файлов.
Определение значков, ассоциированных с файламиРассмотрим еще один пример, позволяющий получить значок файла, отображаемый, например, в Проводнике Windows. Приведенная в листинге 4.35 функция принимает в качестве параметра путь файла и флаг, определяющий, какой нужен значок – малый или большой. Эта функция возвращает дескриптор экземпляра значка, ассоциированного с файлом. Реализация функции представлена в модуле ShellFunctions, расположенном на диске, в папке с названием раздела.
Листинг 4.35. Определение значка файла
function GetFileIcon(filename: String; small: Boolean = False): HICON;
var
info: SHFILEINFO;
flags: Cardinal;
begin
flags:= SHGFI_ICON;
if small then
//Получение малого значка
flags:= flags or SHGFI_SMALLICON
else
//Получение большого значка
flags:= flags or SHGFI_LARGEICON;
ZeroMemory(Addr(info), SizeOf(info));
//Получение значка
SHGetFileInfo(PAnsiChar(filename), 0, info, SizeOf(info), flags);
GetFileIcon:= info.hIcon;
end;
Используемая в листинге 4.35 API-функция SHGetFilelnfo объявлена в модуле ShellApi. В этом же модуле объявлена и структура SHFILEINFO.
В листинге 4.36 приведен пример использования функции GetFileIcon: здесь полученные значки сохраняются в элементах управления Image (по одному для большого и маленького значка).
Листинг 4.36. Пример получения значка заданного файла (или папки)
procedure TForm1.cmbLoadIconClick(Sender: TObject);
begin
//Определение большого и малого значков файла
imgLarge.Picture.Icon.Handle:= GetFileIcon(txtFile.Text);
imgSmall.Picture.Icon.Handle:= GetFileIcon(txtFile.Text, True);
end;
На рис. 4.10 показан пример определения значка файла.
Рис. 4.10. Определение значка, ассоциированного с файлом
На самом деле функция, представленная в листинге 4.35, может определять значки не только файлов, но и папок, дисков и виртуальных папок (Мой компьютер, Рабочий стол, Панель управления и т. д.). Правда, в последнем случае используемая в листинге API-функция SHGetFileInfo потребует первый параметр специального вида (не строка). Работа с таким представлением путей частично рассмотрена в подразделе «Окно для выбора папки» главы 2 (стр. 77).
В заключение стоит сказать несколько слов о прочих полезных возможностях API-функции SHGetFileInfo. Недаром она называется не SHGetFileIcon или как-то иначе: она позволяет получить гораздо больше информации, нежели просто значок файла. Состав возвращаемой функцией информации зависит от набора флагов, передаваемых в функцию в качестве последнего параметра, но сначала рассмотрим поля, из которых состоит структура SHFILEINFO, поскольку результат (за редким исключением) помещается именно в ее поля:
• hIcon (типа HICON) – содержит дескриптор значка объекта, заданного путем (первый параметр функции SHGetFileInfo);
• iIcon (типа Integer) – содержит номер значка, отображаемый в системном компоненте ImageList;
• dwAttributes (типа DWORD) – содержит атрибуты объекта, заданного путем;
• szDisplayName (типа array [0..MAX_PATH-1] of AnsiChar) – буфер для имени заданного объекта (например, сочетание имени и метки диска, отображаемое в Проводнике Windows);
• szTypeName (типа array [0..7 9] of AnsiChar) – буфер для названия типа файла (например, Документ Microsoft Word).
Поля dwAttributes и ilcon подробно описывать не стану. Вместо этого остановлюсь на способе, позволяющем заставить функцию SHGetFilelnfo заполнить остальные поля структуры (их проще всего использовать в Delphi). Используемые для этого флаги (имена целочисленных констант) следующие.
• SHGFI_ICON – поле hIcon заполняется дескриптором значка, ассоциированного с объектом. Если при использовании данного флага дескриптор не сохраняется в каком-либо контейнере или прочем объекте, автоматически удаляющем ненужные значки (как в листинге 4.36), то значок после использования необходимо удалить вручную (для удаления значков предназначена API-функция DestroyIcon).
• SHGFI_LARGEICON, SHGFI_SMALLICON – применяются в сочетании с флагом SHGFI_ICON для получения большого или малого значка соответственно. Совместное использование флагов не имеет смысла (при этом будет получен малый значок).
• SHGFI_DISPLAYNAME – при наличии этого флага в поле szDisplayName будет помещено имя объекта (например, System(C:)).
• SHGFI_EXETYPE – при наличии этого атрибута поле s zTypeName будет заполнено текстовым описанием типа файла.
Значения в приведенном списке можно, если не сказано иного, комбинировать с помощью операции битового ИЛИ (Or).
Извлечение значков из EXE– и DLL-файловНаверняка вы знаете, что исполняемый файл, помимо кода программы, данных и прочей системной информации, может содержать также ресурсы. Из секции ресурсов берутся значки для EXE-файлов. Кроме того, в EXE– и DLL-файлах содержатся значки, используемые для ассоциированных с приложениями документов. Итак, в завершение главы будет рассмотрен еще один пример: создадим программу, способную извлекать упомянутые значки из DLL– и EXE-файлов (а также из ICO-файлов).
Пусть есть путь файла, а также два списка (ImageList) для больших и малых значков соответственно. Тогда процедура, заполняющая списки значками, извлеченными из файла, может выглядеть следующим образом (листинг 4.37).
Листинг 4.37. Составление списков значков
procedure LoadIcons(filename: String; lgImages, smImages: TImageList);
var
icon: TIcon;
smIconHandle, lgIconHandle: HICON;
i: Integer;
begin
//Загрузка каждого значка (неоптимально, но просто)
i:= 0;
while Integer(
ExtractIconEx(PAnsiChar(filename), i, lgIconHandle, smIconHandle, 1)
) > 0 do
begin
Inc(i);
//Большой значок
icon:= TIcon.Create;
icon.Handle:= lgIconHandle;
lgImages.AddIcon(icon);
//Малый значок
icon:= TIcon.Create;
icon.Handle:= smIconHandle;
smImages.AddIcon(icon);
end;
end;
В листинге 4.37 для извлечения значков из файла используется очередная функция модуля ShellApi – ExtractlconEx. Прототип функции имеет следующий вид:
function n n ExtractIconEx (lpszFile: PChar; nIconIndex: Integer;
var phiconLarge, phiconSmall: HICON;
nIcons: UINT): UINT;
Функция ExtractlconEx принимает следующие параметры:
• lpszFile – путь файла, из которого извлекаются значки;
• nIconIndex – номер первого извлекаемого значка; нумерация начинается с нуля (если номер равен -1 и параметры piconLarge и piconSmall нулевые, то функция возвращает количество значков в файле);
• piconLarge, piconSmall – ссылки на переменные типа HICON (либо на первые элементы массива array. of HICON) для помещения в них дескрипторов больших и малых значков соответственно;
• nIcons – количество извлекаемых значков (по сути, может быть количеством элементов в передаваемых в функцию массивах – лишние элементы не будут заполнены).
Функция возвращает количество значков, извлеченных из файла, или количество значков в файле при соответствующем значении параметра nIconlndex.
В листинге 4.36 используется не совсем оптимальный способ извлечения значков из файла – по одному. Однако данный способ подходит для большинства случаев. Другой (но не единственный вариант) – использование массива. В этом случае функции ExtractlconEx передаются первые элементы массивов для дескрипторов значков (функции нужен адрес начала массива), а в качестве последнего параметра – количество элементов в массиве. Таким образом, если количество значков в файле превосходит количество элементов в массиве, вызов функции ExtractlconEx можно будет повторить, передав в качестве параметра nI conlndex значение, возвращенное функцией ExtractlconEx, умноженное на номер вызова функции (начиная с нуля).
Кроме того, можно использовать динамический массив, предварительно установив его размер, вызвав для этого функцию ExtractlconEx с параметром nIconlndex, равным -1. Установить в качестве значения параметров piconLarge, piconSmall ноль (не меняя объявление функции) можно, объявив указатель на HICON (A HICON), присвоив ему значение nil и передав его в качестве упомянутых параметров в функцию.
На рис. 4.11 показан внешний вид формы приложения после извлечения значков из файла Explorer.exe.
Рис. 4.11. Извлеченные из EXE-файла значки
Обработчик нажатия кнопки Загрузить значки формы, показанной на рис. 4.11, приведен в листинге 4.38.
Листинг 4.38. Составление списков значков и их отображение
procedureTForm1. cmbLoadIconClick(Sender: TObject);
var
i: Integer;
item: TListItem;
begin
lvwIconsLg.Clear;
lvwIconsSm.Clear;
//Загрузка значков в ImageList
ImageListLg.Clear;
ImageListSm.Clear;
LoadIcons(txtFile.Text, ImageListLg, ImageListSm);
//Создание элементов в ListView с большими и малыми значками
for i:= 0 to ImageListLg.Count – 1 do
begin
item:= lvwIconsLg.Items.Add();
item.Caption:= 'Icon'+ IntToStr(i+1);
item.ImageIndex:= i;
item:= lvwIconsSm.Items.Add();
item.Caption:= 'Icon'+ IntToStr(i+1);
item.ImageIndex:= i;
end;
end;
Здесь подразумевается, что lvwIconLg и lvwIconSm – имена элементов управления ListView для отображения больших и малых значков соответственно. На форме также расположены два элемента управления ImageList для хранения больших и малых значков соответственно: ImageListLg и ImageListSm.
С помощью окна Object Inspector список ImageListLg назначен в качестве источника больших изображений (свойство LargeImages) для lvwIconLg. Соответственно, список ImageListSm назначен в качестве источника малых изображений (свойство SmallImages) для lvwIconSm.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.