Delphi копирование файлов по маске

Обновлено: 07.07.2024

Нет другого такого процесса в программе инсталляции, который бы выглядел снаружи столь простым и был бы в реализации столь сложным, как копирование. Сложная фраза. В переводе на русский язык она означает, что сделать хорошую операцию копирования файлов нелегко :) Почему?

Потому что операция копирования файлов должна быть идеальной.

Начнём с самого просто случая — копирования одного файла. Для того, чтобы скопировать один файл, вы можете вызвать функцию Windows, которая называется CopyFile: Как видим, функции необходимо передать имена двух файлов: исходного и целевого. Третий параметр отвечает за то, как функция будет поступать, если целевой файл уже существует. Значение True говорит о том, что функция не будет копировать файл, значение False — о том, что целевой файл будет перезаписан.

Функция возвращает True, если операция копирования была успешно выполнена. Значение False подсказывает нам, что необходимо вызвать функцию GetLastError для того, чтобы узнать код произошедшей ошибки.

Эта функция работает очень надёжно, поскольку она является частью операционной системы и практически наверняка именно она тестировалась огромное количетво раз. Тем не менее, первое, на чём мы остановимся — что в этой функции нас не устраивает? У этой функции один недостаток, но он способен перекрыть все её достоинства. Мы не имеем доступа к процессу копирования. Это означает, что мы не можем показывать индикатор процесса копирования и не можем прервать функцию CopyFile, если пользователь нажал кнопку "Отмена" или клавишу Escape.

Насколько это важно? На этот вопрос вы вольны отвечать самостоятельно. Если вам потребуется копировать большое количество маленьких файлов, то для вас этот недостаток неважен. Если вы собираетесь копировать файлы размером в несколько мегабайт, то у пользователя вашей программы могут возникнуть определённые проблемы.

В Windows NT 4.0 появилась новая функция — CopyFileEx, которая позволяет снять все появившиеся проблемы, но добавляет ещё одну — она не работает в Windows 95. :)

В Delphi для копирования файлов, мы можем воспользоваться объектами класса

TFileStream. Например, так: Обработку ошибок, как говорится в таких случаях, я оставляю читателю в качестве упражнения. :)

Немного поговорим о приведённой процедуре. Саму операцию копирования выполняет метод CopyFrom. Если второй параметр этого метода равен нулю, то копируется сразу весь файл, в ином случае копируется указанное количество байтов. Мы можем копировать файл блоками, со всеми вытекающими отсюда преимуществами: Замечательные функции FileGetDate, FileSetDate, FileGetAttr, FileSetAttr выполняют очень важную работу, про которую не надо забывать: копируют дату создания файла и его атрибуты. Сейчас мы не будем углубляться в особенности файловой системы NTFS, в которой есть дата последней модификации файла и расширенные атрибуты, поскольку приведённого кода нам вполне для наших целей хватит. Примечание: Корректное копирование предполагает, что у целевого файла обязательно устанавливается флаг Archive. Я не буду углуляться в то, зачем это делается, тем более, что в нашем случае этого делать не обязательно.

Тем не менее, если вы хотите реализовать полноценную операцию копирования, не забудьте про эту маленькую особенность.

Сейчас я хочу показать вам, как в Delphi работать с файлами (изменять, удалять, копировать, переименовывать), ниже будет представлен исходник программы и если у вас что-то не получиться запрограммировать, то вы всегда сможете посмотреть этот исходник.

За копирование файлов в Delphi отвечает функция CopyFile, она имеет следующий синтаксис:
CopyFile(Начальный_файл, Конечный_файл, Перезапись);

Где,
Начальный_файл - Полный путь с указанием имени и расширения к файлу, который будет копироваться.
Конечный_файл - Полный путь с указанием имени и расширения куда копируем.
Перезапись – Если такой файл уже существует, то будет ли он перезаписан (true - не будет, false - будет).

Пример:

CopyFile('C:\1.txt', 'D:\1.txt', true);

Обратите внимание, что при указании второго параметра (Конечный_файл) мы указываем не просто папку куда хотим скопировать файл, но и еще желаемое имя с расширение файла. Т.е если Начальный файл c:\1.txt, то если указать имя конечного файла как d:\1Copy.txt то в процессе копирования наш 1.txt переименуется в 1Copy.txt.

За переименование файлов в Delphi отвечает функция RenameFileсинтаксис у неё очень простой и чем то схож с функцией копирования.

RenameFile('Начальное_имя','Конечное_имя')
Начальное_имя - Полный путь с указанием имени и расширения, к файлу, который будет переименован.
Конечное_имя - Полный путь к файлу с указанием нового имени и расширения.

Пример:

Что бы переместить файл, в Delphi используется функция MoveFile. Давайте посмотрим на её синтаксис:
MoveFile(Начальный_файл, Конечный_файл);

Где,
Начальный_файл - Полный путь с указанием имени и расширения к файлу, который будет перемещаться.
Конечный_файл - Полный путь с указанием имени и расширения куда перемещаем.

Здесь также следует обратить внимание на то что при указании второго параметра (Конечный_файл) мы указываем не просто папку куда хотим переместить файл, но и еще желаемое имя с расширение файла. Т.е если Начальный файл c:\1.txt, то если указать имя конечного файла как d:\1Paste.txt то в процессе перемещения наш 1.txt переименуется в 1Paste.txt.

Наверное, самая простая из рассмотренных выше функций это функция удаления, DeleteFile.

DeleteFile('Имя_файла');
Имя_файла - здесь предполагается указание полного пути, имени и расширения удаляемого файла.

В данной статье мы подробно рассмотрим применение функции SHFileOperation. Данная функция позволяет производить копирование, перемещение, переименование и удаление (в том числе и в Recycle Bin) объектов файловой системы.
Функция возвращает 0, если операция выполнена успешно, и ненулевое значение в противном :-) случае.

  • FO_COPY Копирует файлы, указанные в pFrom в папку, указанную в pTo .
  • FO_DELETE Удаляет файлы, указанные pFrom ( pTo игнорируется).
  • FO_MOVE Перемещает файлы, указанные в pFrom в папку, указанную в pTo .
  • FO_RENAME Переименовывает файлы, указанные в pFrom .

pTo
Аналогично pFrom , но содержит путь к директории - адресату, в которую производится копирование или перемещение файлов. Также может содержать несколько путей. При этом нужно установить флаг FOF_MULTIDESTFILES .

hNameMappings
Дескриптор объекта отображения имени файла, который содержит массив структур SHNAMEMAPPING . Каждая структура содержит старые и новые имена пути для каждого файла, который перемещался, скопирован, или переименован. Этот элемент используется только, если установлен флаг FOF_WANTMAPPINGHANDLE .

lpszProgressTitle
Указатель на строку, используемую как заголовок для диалогового окна прогресса. Этот элемент используется только, если установлен флаг FOF_SIMPLEPROGRESS .

Примечание.
Если pFrom или pTo не указаны, берутся файлы из текущей директории. Текущую директорию можно установить с помощью функции SetCurrentDirectory и получить функцией GetCurrentDirectory .

Разумеется, вам нужно вставить в секцию uses модуль ShellAPI , в котором определена функция SHFileOperation .

Рассмотрим самое простое - удаление файлов .

Обратите внимание, что ни один из флагов не установлен. Если вы хотите не просто удалить файлы, а переместить их в корзину, должен быть установлен флаг FOF_ALLOWUNDO .

Для удобства дальнейших экспериментов напишем функцию, создающую из массива строк буфер для передачи его в качестве параметра pFrom. После каждой строки в буфер вставляется нулевой байт, в конце списка - два нулевых байта. Выглядит ужасно, но работает. Можно написать красивее, просто лень.

И, наконец, функция, удаляющая файлы, переданные ей в списке Names. Параметр ToRecycle определяет, будут ли файлы перемещены в корзину или удалены. Функция возвращает 0, если операция выполнена успешно, и ненулевое значение, если руки у кого-то растут не из того места, и этот кто-то всунул функции имена несуществующих файлов. Обратите внимание, что мы освобождаем буфер Src простым присваиванием значения nil. Если верить документации, потери памяти при этом не происходит, а напротив, происходит корректное уничтожение динамического массива. Каким образом, правда - это рак мозга :-).

Проверяем : Вроде все работает.

Кстати, обнаружился забавный глюк - вызовем процедуру DeleteFiles таким образом: Файлы 'Test1' и 'Test2' удаляются совсем, без помещения в корзину, несмотря на установленный флаг FOF_ALLOWUNDO . Мораль: при использовании функции SHFileOperation используйте полные пути всегда, когда это возможно.
Ну, с удалением файлов разобрались.

Теперь очередь за копированием и перемещением.

Следующая функция перемещает файлы указанные в списке Src в директорию Dest . Параметр Move определяет, будут ли файлы перемещаться или копироваться. Параметр AutoRename указывает, переименовывать ли файлы в случае конфликта имен. Ну, проверим. Все в порядке (а кудa ж оно денется).

Есть, правда еще одна возможность - перемещать много файлов каждый в свою директорию за один присест, но я с трудом представляю, кому это может понадобиться.

Осталась последняя операция - переименование.

Пока все .
Mодуль FileOp.pas (3K) прилагается.

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Посмотрел, но там ничего о такой ситуации не описано

Здравствуйте, Александр Т, Вы писали:

АТ>Здравствуйте, liver, Вы писали:

L>>не так уж сложно сделать свою форму, которая будет выводить подобную инфу.
L>>даже покрасивше можно сваять
L>>хотя велосипед изобретать дело не благодарное

АТ>Но как мне методом обхода каталогов установить максимум в ProgressBare?

Да, вопрос по делу. Даже не знаю как ответить
Ну яб сделал так .
Можнож апишными функциями или еще как, определить количество файлов в папке, как это делает винда.
и брать это колличество за максимум.
или размер папки за максимум.
это как вариант.

Может я что-то не понял, но такой маской ты задаешь копировать все файлы из папки "C:\Папка" в папку "D:\Папка2", так причем здесь каталоги ?

Вдогонку: если хочешь чтобы работала маска *.* необходимо поставить флаг FOF_FILESONLY, который определяет: операция будет производиться только с файлами.

Здравствуйте, OlegProv, Вы писали:

OP>Может я что-то не понял, но такой маской ты задаешь копировать все файлы из папки "C:\Папка" в папку "D:\Папка2", так причем здесь каталоги ?

Вырезка из MSDN:

FOF_FILESONLY
Perform the operation on files only if a wildcard file name (*.*) is specified. Т.е. если указан этот флаг, то обрабатываются только файлы.


Флаг FOF_FILESONLY не указан. Обрабатываются вложенные каталоги.

Зачем задавать маску C:\Папка\*.* , чтобы скопировать еще и подкаталоги папки "Папка" ?
Не проще ли записать FromFile=C:\Папка
Я так понял, что маска *.* работает именно только для файлов и с флагом FOF_FILESONLY с другими флагами типа: F.fFlags:=FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR;
она работать не будет.

Здравствуйте, OlegProv, Вы писали:

OP>Зачем задавать маску C:\Папка\*.* , чтобы скопировать еще и подкаталоги папки "Папка" ?
OP>Не проще ли записать FromFile=C:\Папка
OP>Я так понял, что маска *.* работает именно только для файлов и с флагом FOF_FILESONLY с другими флагами типа: F.fFlags:=FOF_NOCONFIRMATION or FOF_NOCONFIRMMKDIR;
OP>она работать не будет.

Работать то она работает, я ж описал в самом начале, что копируются папки с подпапками, но только если в корневой папке есть хоть один файл. А вот написать FromFile=C:\Папка не получается.

---------------------------
Ошибка при копировании файла или папки
---------------------------
Не удается создать либо заменить "Папка". Не удается найти путь.

вот продцедура обхода всех директорий с файлами по указаному пути.

Procedure ScanDir(StartDir: String; Mask: String; FileName: String);
Var
SearchRec: TSearchRec;
Begin
If Mask = '' Then Mask:= '*.*';
If StartDir[Length(StartDir)] <> '\' Then StartDir:= StartDir + '\';
If FindFirst(StartDir+Mask, faAnyFile, SearchRec) = 0 Then
Begin
Try
Repeat
Application.ProcessMessages;
If (SearchRec.Attr And faDirectory) <> faDirectory Then

BEGIN
//StartDir + SearchRec.Name
//Полный путь к файлу
//соответственно мы его копируем заменяя чать пути
END
Else If (SearchRec.Name <> '..') And (SearchRec.Name <> '.') Then
BEGIN
ScanDir(StartDir + SearchRec.Name + '\', Mask, FileName);
//Ну а здесь мы имеем имя директории
END

Until FindNext(SearchRec) <> 0;
Finally
FindClose(SearchRec);
End;
End;
End;

var s: String;
Begin
ScanDir('D:\tmp', '*.*', s);
End;

function CopyFiles( Handle : Hwnd; Src : array of string; Dest : string; Move : Boolean; AutoRename : Boolean ) : Integer;
var
SHFileOpStruct : TSHFileOpStruct;
SrcBuf : TBuffer;
begin
CreateBuffer( Src, SrcBuf );
with SHFileOpStruct do
begin
Wnd := Handle;
wFunc := FO_COPY;
if Move then wFunc := FO_MOVE;
pFrom := Pointer( SrcBuf );
pTo := PChar( Dest );
fFlags := 0;
if AutoRename then fFlags := FOF_RENAMEONCOLLISION;
fAnyOperationsAborted := False;
hNameMappings := nil;
lpszProgressTitle := nil;
end;
Result := SHFileOperation( SHFileOpStruct );
SrcBuf := nil;
end;
Проверяем:

procedure TForm1.Button1Click(Sender: TObject);
begin
CopyFiles( Handle, [ ‘C:Test1’, ‘C:Test2’ ], ‘C:Temp’, False, False );
end;

Сам не пробовал, но говорят работает

Здравствуйте, liver, Вы писали:

L>вот продцедура обхода всех директорий с файлами по указаному пути.

L>Procedure ScanDir(StartDir: String; Mask: String; FileName: String);
L>Var
L> SearchRec: TSearchRec;
L>Begin
L> If Mask = '' Then Mask:= '*.*';
L> If StartDir[Length(StartDir)] <> '\' Then StartDir:= StartDir + '\';
L> If FindFirst(StartDir+Mask, faAnyFile, SearchRec) = 0 Then
L> Begin
L> Try
L> Repeat
L> Application.ProcessMessages;
L> If (SearchRec.Attr And faDirectory) <> faDirectory Then

L>BEGIN
L>//StartDir + SearchRec.Name
L>//Полный путь к файлу
L>//соответственно мы его копируем заменяя чать пути
L>END
L> Else If (SearchRec.Name <> '..') And (SearchRec.Name <> '.') Then
L>BEGIN
L>ScanDir(StartDir + SearchRec.Name + '\', Mask, FileName);
L>//Ну а здесь мы имеем имя директории
L>END

L> Until FindNext(SearchRec) <> 0;
L> Finally
L> FindClose(SearchRec);
L> End;
L> End;
L>End;


Спасибо, конечно. но у меня есть похожая процедура.
Здесь имеется 2 вопроса:
1. Зачем параметр FileName?
2. Как отображать прогресс копирования?

можно заменить на

Здравствуйте, Александр Т, Вы писали:

АТ>Здравствуйте, liver, Вы писали:

L>>вот продцедура обхода всех директорий с файлами по указаному пути.

L>>Procedure ScanDir(StartDir: String; Mask: String; FileName: String);
L>>Var
L>> SearchRec: TSearchRec;
L>>Begin
L>> If Mask = '' Then Mask:= '*.*';
L>> If StartDir[Length(StartDir)] <> '\' Then StartDir:= StartDir + '\';
L>> If FindFirst(StartDir+Mask, faAnyFile, SearchRec) = 0 Then
L>> Begin
L>> Try
L>> Repeat
L>> Application.ProcessMessages;
L>> If (SearchRec.Attr And faDirectory) <> faDirectory Then

L>>BEGIN
L>>//StartDir + SearchRec.Name
L>>//Полный путь к файлу
L>>//соответственно мы его копируем заменяя чать пути
L>>END
L>> Else If (SearchRec.Name <> '..') And (SearchRec.Name <> '.') Then
L>>BEGIN
L>>ScanDir(StartDir + SearchRec.Name + '\', Mask, FileName);
L>>//Ну а здесь мы имеем имя директории
L>>END

L>> Until FindNext(SearchRec) <> 0;
L>> Finally
L>> FindClose(SearchRec);
L>> End;
L>> End;
L>>End;

АТ>Спасибо, конечно. но у меня есть похожая процедура.
АТ>Здесь имеется 2 вопроса:
АТ>1. Зачем параметр FileName?
АТ>2. Как отображать прогресс копирования?

АТ>И один comment

АТ>строку
АТ>
АТ>можно заменить на
АТ>


по поводу прогрессбара я уже писал.
а по поводу комента, не знаю, не пробовал.

Читайте также: