Главная
 Сайт Андрея Зайчикова
Пятница, 1 Февраля 2008г. 
Карта сайта Поиск по сайту Написать письмо  
 .:Навигатор 
Новости
Библиотека
Статьи
Олимпиады
FAQ (ЧаВо)
Гостевая книга 
Ссылки
 .:Информация 


Магия функций с префиксом SH (Win32 SDK)

Петр Семилетов
Не так уж давно я - с упоением алхимика, обращающего свинец в золото - ваял свой собственный файловый менеджер, по образу и подобию FAR'а и DOS Навигатора. И, конечно же, потребовалось - как минимум - реализовать стандартные функции работы с файлами: копирование, перенос, удаление, и т.п. Еще пригодились бы методы доступа к системным папкам и некоторые стандартные диалоги (которые я видел в других программах). Естественно, самому мне лень было думать или искать что-либо; и я решил обратиться с беспокоящим меня вопросом в несколько эхоконференций, посвященных Delphi и программированию в Windows - где обнаружил, что данная тема вовсю обсуждается, и прочел не меньше дюжины гениальных советов.

В частности, для тривиального, на первый взгляд, копирования файлов советовали "читать блоками один файл, и писать блоками в другой". Еще один безликий голос вещал: "берешь, создаешь TMemoryStream, ‘всасываешь’ в него файл, а потом сохраняешь поток в файл назначения, вот".

Ну, вариант "всасывания" файла я отбросил сразу же; а над поблочным чтением задумался. Древняя истина гласит: чем больше я знаю - тем больше я не знаю. Как лучше реализовать копирование целых папок, вместе с вложенными в них директориями? А то же для переноса, удаления?..

На ум пришли унылые алгоритмы работы со списками, и радость творчества померкла. Перспектива вырисовывалась мрачная: придется работать… Однако, если верить буддистам и закон кармы существует, в прошлой жизни я совершил нечто хорошее, потому что путь познания привел меня прямиком к мощной штуке под названием Microsoft Win32 Software Development Kit. Вот тут все и вошло в русло...

Итак, далее в статье при описании функции будем сначала приводить ее прототип на C++, описанный в shellapi.h, а затем - объяснять назначение (возможно, с примером использования в Delphi). Кстати, пользователям последнего продукта надо в секцию uses вписать юнит "shellapi"; и, кроме того, под WindowsNT функции, о которых пойдет речь, не работают…

Ну-с; начнем, пожалуй, с изящной

WINSHELLAPI int WINAPI SHFileOperation(LPSHFILEOPSTRUCT lpFileOp);

Это очень, очень мощная и многофункциональная вещь, которая умеет копировать, переносить, переименовывать и удалять как отдельные файлы, так и папки вместе с их содержимым. Хуже того - поддерживаются даже маски с wild-characters.

Наша задача - только заполнить перед вызовом поля структуры lpFileOp; больше всего нас волнуют следующие поля:
HWND hwnd (то есть hwnd: integer) - хэндл владельца диалогового окна, которое будет использоваться для вывода информации о статусе операции. В Delphi можно использовать свойство формы handle.
UINT wFunc - код выполняемой операции (FO_COPY, FO_DELETE, FO_MOVE, FO_RENAME).
LPCSTR pFrom, LPCSTR pTo - пути: откуда и куда копируются, переносятся или переименовываются файлы. Напомню, что в описании для Object Pascal SHFILEOPSTRUCT именуется TSHFILEOPSTRUCT, а типу LPCSRT поставлен в соответствие PAnsiChar - посему pFrom и pTo имеют тип PAnsiChar. Чтобы привести к нему обычную строку, нужно написать нечто вроде pFrom := PChar(strPath);
Вместо конкретного пути в каждом из параметров может быть передан путь с маской, либо список файлов - в последнем случае их имена надлежит разделять пробелами, а завершить двумя нуль-терминаторами (#0#0). Если путь к файлам не указан явно, то по умолчанию берется путь к текущей папке.
FILEOP_FLAGS fFlags - весьма полезные флаги. Их много очень; в частности:
- FOF_FILESONLY - производить операцию только над файлами,
- FOF_SILENT - не отображать окно с прогрессом выполнения действия,
- FOF_SIMPLEPROGRESS - показывать его, но не отображать имена файлов,
- FOF_NOCONFIRMATION - отвечать 'Да для всех" при любом запросе у пользователя, 
- FOF_RENAMEONCOLLISION - при совпадении имен файла из pFrom и pTo создавать выходной файл с именем вида "Копия <имя_оригинала>".
BOOL fAnyOperationsAborted - после выхода из функции устанавливается в TRUE, если операция была прервана пользователем.
LPCSTR lpszProgressTitle - заголовок статусного окна с прогрессом выполнения. Отображается только если установлен флаг FOF_SIMPLEPROGRESS.

Для наглядности - вот пример функции-оболочки для копирования файлов на Object Pascal:

//uses ShellAPI;

function SimpleCopyFiles(_pfrom,_pto: string): boolean;
var
lpFileOp: TSHFILEOPSTRUCT;
begin
with lpFileOp do
begin
Wnd:=form1.handle; //для очистки совести
wFunc:=FO_COPY; //просто копируем
pFrom:=PChar(_pfrom); //копируем отсюда
pTo:=PChar(_pto); //вот сюда
fFlags:=0; //чистим флаги - мусор нам ни к чему
fFlags:=(fFlags OR FOF_RENAMEONCOLLISION);
end;
if SHFileOperation(lpFileOp)<>0 then Result:=FALSE
else Result:=TRUE;
end;

SHFileOperation в случае неудачи возвращает ненулевое значение; мы это учитываем, и возвращаем Result = False, если что-то не в порядке.

Другая полезная функция из ShellAPI (на этот раз объявленная в shlobj.h) - SHBrowseForFolder, позволяющая культурно выбрать нужную папку (рис. 1). Эта функция часто используется программистами - например, с ее помощью выбираются директории в AVP.Итак, представляем:

WINSHELLAPI LPITEMIDLIST WINAPI SHBrowseForFolder(LPBROWSEINFO lpbi).

Пользователям Delphi придется для ее использования добавить в секцию uses shellAPI, comobj и shlobj.
В качестве параметра эта функция требует экземпляр громоздкой структуры, о которой мы сейчас поговорим поподробнее, разбирая очередной пример - функцию SimpleSelectFolder.

Вначале опишем одну callback-функцию:

procedurecb(wnd:HWND;uMsg:UINT;lParam,lpData:LPARAM);stdcall;
var s: String;
begin
s:='Выберите папку, пожалуйста';
SendMessage(wnd,BFFM_SETSTATUSTEXT,0,longint(@s[1]));
end;

Это для того, чтобы вывести в окне родителя (т.е. диалога выбора папки) приглашение-подсказку (посылаем соответствующее сообщение в родительское окно).

Далее, сама функция SimpleSelectFolder будет иметь вид:

function SimpleSelectFolder(h: HWND): string;
var
bi: TBrowseInfo;
s: PChar; // сюда записывается имя выбранной папки
RootPIDL: PITEMIDLIST;
Path: array [0..MAX_PATH-1] of Char;
ResultPIDL: PITEMIDLIST;

begin
result:=''; 
//определяем локацию специальной папки, в данном случае
//CSIDL_DRIVES - содержимое My Computer
SHGetSPecialFolderLocation(h,CSIDL_DRIVES,RootPIDL);
s:=StrAlloc(MAX_PATH); //выделим память для имени папки

with bi do //заполняем структуру bi типа TBrowseInfo
begin
//владелец окна
hwndOwner:=h;
//куда передать имя папки после выбора?
pszDisplayName:=s; //вот сюда
//лишнее напоминание пользователю, можно не заполнять
lpszTitle:='Выбор папки';
//если нужен вызов callback-функции cb, выставить флаг
ulFlags:=BIF_STATUSTEXT;
//PIDL локации, в данном случае My Computer'а
pidlroot:=RootPIDL;
//ссылка на функцию cb, а если она не нужна, lpfn:=nil
lpfn:=@cb;
end;

//вызов нашей героини, результат типа LPITEMIDLIST
resultPIDL:=SHBrowseForFolder(bi);
//конвертируем ResultPIDL в нормальный путь
SHGetPathFromIDList(resultPIDL,@Path[0]);
//приводим полученный путь к типу String
result:=String(@Path[0]);
StrDispose(s); //и чистим память
end;

Вуаля! теперь вызов StrMyFolder:=SimpleSelectFolder(handle) приведет к отображению диалога выбора директории - солидно, как в лучших домах Парижа.

Пожалуй, на этом обзор простых функций из ShellAPI для работы с файлами пора завершать, поскольку функций с префиксом SH гораздо больше, чем я описал (загляните-ка в help по Win32 SDK…).
Разве что могу предложить - на десерт - блюдо под названием

WINSHELLAPI void WINAPI SHAddToRecentDocs(UINT uFlags, LPCVOID pv),

служащее для добавления документа в папку меню Пуск|Документы ("Start|Documents"). На практике это может выглядеть примерно так:

SHAddToRecentDocs(SHARD_PATH, PChar('C:\Books\Суворов\Аквариум.txt'));

Первый параметр - флаг, объясняющий, что именно представляет из себя объект, который мы просим занести в Recent: обычный путь (SHARD_PATH) или PIDL (SHARD_PIDL);второй же параметр - собственно указатель на данные. Если последний равен nil, то весь список документов очищается. Вот такая полезная вещь.

Ну-с, надеюсь, вы почерпнули из этого материала полезную для себя информацию.
Все примеры в статье протестированы и должны работать.

 
 © Андрей Зайчиков