Как указать путь к файлу delphi
Сейчас я хочу показать вам, как в 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('Имя_файла');
Имя_файла - здесь предполагается указание полного пути, имени и расширения удаляемого файла.
Delphi site: daily Delphi-news, documentation, articles, review, interview, computer humor.
В реальных программах иногда необходимо копировать, перемещать и удалять файлы. В Delphi для этих целей служат очень простые функции.
Эта функция возвращает true, если операция прошла успешно, и false, если неудачно.
Функция DeleteFiie умеет удалять только файлы и только по одному. У вас не получится работать сразу с несколькими файлами, и придется для каждого из них вызывать функцию удаления. Помимо этого, можно удалять только файлы. Если указать директорию, то операция не будет выполнена.
Для удаления директорий есть отдельная функция:
Функция возвращает true, если операция прошла успешно, и false, если неудачно.
Когда мы не указываем полный путь, а только имя файла или директории, то функции ищут эти файлы в текущей папке. Для изменения текущей папки служит функция chDir:
ChDir('Путь к папке, которая будет использоваться по умолчанию'); Это процедура, и у нее нет возвращаемого значения.
Текущую для программы директорию можно узнать с помощью функции GetCurrentDir, которой не надо ничего передавать, она просто возвращает текущую директорию.
Перед операциями над файлами и директориями желательно убедиться в их существовании. Для того чтобы узнать, существует ли файл, можно воспользоваться следующей функцией:
FileExists " 'Имя или полный путь к файлу');.
Если файл существует, то функция вернет true, иначе - false.
Узнать о существовании директории можно с помощью следующей функции:
Если директория существует, то вернет true, иначе - false.
Вот небольшой пример использования описанных функций:
В этом примере сначала изменяется текущая директория на корень диска С. После этого происходит проверка: если существует файл autoexec.bat, то он удаляется из текущей директории.
Использовать данные функции Delphi очень просто, но они имеют слишком мало возможностей и среди них нет хорошей функции для копирования и перемещения файлов. Среди справки Delphi можно найти описание функций копирования и перемещения, которые можно использовать в проектах. Для этого нужно только добавить их в свой проект.
Source, Dest: Integer; Len: Integer; Destination: const
GetMem(CopyBuffer, ChunkSize) ; try
Source:»FileOpen(FileName, fmShareDenyWrite);//Открыть файл-источник if Source " 0 then raise EFOpenError.CreateFmt(SFOpenError, [FileName]); try
Dest := FileCreate(Destination); //Создать файл-приемник if Dest " 0 then raise try
//Считать порцию файла
BytesCopied'.=FiieRead(Source,CopyBufЕегЛ,ChunkSize); if BytesCopied " 0 then //Если порция считана, то. //Записать ее в файл-приемник FileWrite(Dest, Copy-Buffer", BytesCopied); until BytesCopied " ChunkSize; finally
Процесс копирования очень прост. Процедура получает два имени файла: откуда копировать, и куда. После этого происходит проверка. Если в качестве второго параметра (путь к файлу, в который надо скопировать)' указана только директория без имени файла, то программа подставляет в качестве имени файла имя источника.
После этого источника открывается для чтения данных с запретом на запись со стороны других программ. Открыв источник, процедура создает файл приемника. Если он существовал, то без каких-либо предупреждений файл будет перезаписан. Дальше запускается цикл, в котором из файла источника считываются данные по 8 192 байт и тут же записываются в файл приемника. Таким образом, в цикле происходит копирование файла небольшими порциями. Чем больше порция, тем быстрее будет происходить копирование.
Процедура копирования - очень хороший пример использования функций работы с файлами. Все сделано очень грамотно и великолепно работает, хотя и не очень универсально. Например, нет вызова предупреждения о существовании результирующего файла перед его уничтожением. Но это не так уж сложно сделатьС помощью функции F і 1 е Е х і s t s.
Теперь посмотрим на реализацию функции перемещения файлов (листинг 3.14). |1иС™нг^
Эта функция также получает в качестве параметров два имени файла: источника и приемника. В начале функции происходит попытка переименовать файл источника в приемник. Если оба файла находятся на одном диске, то такая операция произойдет успешно, и файл-источник без копирования превратится в файл-приемник с помощью простого изменения пути расположения.
Если источник и приемник находятся на разных дисках, то такой трюк не пройдет, поэтому процедура вызовет функцию описанную выше, для копирования источника в новое место, а потом удалит файл запуска файла можно использовать следующую универсальную функцию (листинг 3.15).
Чтобы ее использовать, нужно добавить это описание в свой модуль. Только не забудьте добавить еще в раздел uses модуль shellAPi, иначе проект нельзя будет скомпилировать.
У функции четыре параметра.
П Имя ' файла, или полный путь к файлу, который надо запустить.
Вот простой пример использования данной функции:
С помощью этой же функции можно запускать Internet Explorer (или другой браузер, который установлен по умолчанию) и загрузить Интернет-страничку:
Если нужно создать электронное письмо, то это можно сделать следующим способом:
Функцию ShellExecute мы уже рассматривали в разд. 2.5, и все же я решил описать ее еще раз, чтобы выделить в отдельную процедуру. Применяя ее, вам не надо следить за типом pchar, который используется- для передачи строк, потому что наша функция ExecuteFile сама сделает необходимые преобразования.
И сейчас мы перейдем к реальному примеру, который будем .изучать на практике. Создайте новый проект и перенесите на форму два компонента: ShellTreeView И ShellListView. У компонента ShellTreeView В СВОЙСТВе нужно указать компонент чтобы связать в одно целое. У компонента ShellListView нужно установить свойству Mutiselect значение true, чтобы мы могли выбирать несколько файлов.
Теперь нужно добавить панель, на которой мы разместим четыре кнопки: Копировать, Переместить, Удалить, Свойства. Мою форму будущей программы вы можете увидеть на рис. 3.17.
Рис. 3.17. Форма будущей программы работы с несколькими файлами
Теперь перейдите в раздел uses и добавьте туда два модуля: shellapt. и FileCtrl. ПерВЫЙ МОДУЛЬ необходим ДЛЯ работы фуНКЦИИ SHFileOperation. Во ВЮрОМ есть фунКЦИЯ SelectDirectory, которая ВЫЮДИТ на экран стандартное окно выбора директории. Это окно мы будем использовать, когда нужно будет выбрать директорию, в которую надо скопировать или переместить файлы.
В разделе private добавим описание следующей функции:
private < Private declarations >function DoSHFileOp(Handle: THandle; OpMode: UInt; Src, Dest: string; DelRicleBin; Boolean): Boolean; Эта функция будет универсальная: для копирования, перемещения и удаления файлов. Нажмите "Ctrl"+"Shift"+"C", чтобы создать заготовку этой функции. В этой заготовке нужно написать следующее (листинг 3.16).
Листинг3.16. Универсальная функция для работы с файлами Г
function TForml.DoSHFileOp (Handle: THandle; CpMode: UInt; Src,.
Для функции SHFiieOperation нужен только один параметр- структура типа TSHFileOpStruct. Такой переменной является ipFiieOp. Прежде чем использовать эту структуру, мы заполним ее нулями с помощью функции Fiilchar, чтобы там случайно не оказались ненужные данные. Теперь перечислим свойства, которые нужно заполнить.
- путь-источник, который мы получаем в качестве третьего параметра.
рто - путь-приемник, который мы получаем в качестве четвертого параметра.
• fof_simpleprogress - показать окно выполнения процесса, но не отображать имена файлов.
• IpszProgressTitle - текст, который будет отображаться в окне хода выполнения операции.
После раздела vaг и перед ключевым словом implementation напишите следующий код:
FileOpMode: array[0..3] of UInt =
Здесь мы объявили массив из четырех значений. Каждое из значений - это константа для обозначения определенной операции:
Теперь создадим обработчики событий для нажатия кнопок нашей панели. Сначала создайте обработчик события onclick для кнопки Копировать. В нем нужно написать следующий код (листинг 3.17).
for i := 0 to do if (ShellListViewl.items.item[i].Selected) then begin FSrc:=FSrc+
Прежде чем производить попытку копирования, надо проверить, выбрал ли пользователь какие-либо файлы. Если нет, то нужно выйти из процедуры, потому что копировать нечего. Эта проверка происходит во второй строке кода:
После этого на экран выводится окно выбора директории, в которую нужно будет скопировать выбранные файлы. Делается это с помощью функции Если пользователь ничего не выбрал, то происходит выход из процедуры. Внешний вид окна выбора директории вы можете увидеть на рис. 3.18.
Рис. 3.18. Окно выбора директории
Теперь нужно узнать директорию, из которой происходит копирование. Полный путь находится в свойстве path компонента ShellTreeviewl. Также проверяется, если последний символ пути не равен знаку его нужно добавить:
for i := 0 to ShellListViewl.items.Count-1 do if (ShellListViewl. items, item[i] .Selected) then begin FSrc:=FSrc+
После этого вызывается написанная нами ранее процедура DoSHFiieOp, указывая все необходимые параметры. В качестве второго параметра указана операция, которую надо выполнить- FiieOpMode[0], что равно команде focopy. Третий и четвертый параметр - это пути источника и приемника (откуда и куда надо копировать).
Теперь напишем код для кнопки Переместить. Для этого в обработчике события onclick соответствующей кнопки пишем следующее (листинг 3.18).
for i := 0 to ShellListViewl.items.Count-1 do if (ShellListViewl.items.item[il.Selected) then begin FSrc:=FSrc+
Этот код идентичен тому, что мы написали для кнопки Копировать. Разница только в вызове процедуры DoSHFileOp, где мы указываем операцию FileOpMode [2], что означает перемещение. А в остальном там так же определяется директория, из которой нужно копировать, и так же формируется строка из имен файлов для копирования, разделенных нулевым символом.
В обработчике нажатия кнопки Удалить пишем следующий код (листинг 3.19).
for i := 0 to FilesListView.items.Count-1 do if then begin DelFName:=DelFName+
И снова код похож на тот, что мы уже использовали дважды. Но есть все-таки две разницы:
1. Мы проверяем, находится ли какой-нибудь файл в режиме редактирования ЕііезіЛз^іе*.І8Ес!Шп§. Если да, ТО ВЬМДМ ИЗ процедуры.
2. В вызове процедуры ОоЗНЕііеОр в качестве второго параметра мы напрямую указываем константу БорЕЬЕТЕ, хотя можно было бы указать РііеОрМоає [ 1], что абсолютно то же самое.
В обработчике нажатия кнопки Свойства напишем следующий код (листинг 3.20).
Здесь мало строчек кода, но с ним придется разбираться. В начале происходит простая проверка на выделение. Если пользователь ничего не выбрал, то выходим, иначе в следующей строке произойдет ошибка. Дальше мы вызываем функцию SHObjectProperties, которая отображает стандартное окно свойств объекта. У этой функции 4 параметра.
О Указатель на окно-владельца.
О Последний параметр оставляем равным nil.
На рис. 3.19 вы можете увидеть стандартное окно свойств, вызванное из нашей программы.
Рис. 3.19. Стандартное окно свойств объекта
Чтобы проект теперь скомпилировался, нужно сообщить о существовании функции зногое^Ргорегг^ез, о которой Ш1рЫ еще не знает. Для этого создайте файл stan.dardDialogs.pas и напишите в нем следующее (листинг 3.21).
windows, Messages, SHlObj,-
//Cancel the operation and close the dialog.
TSHObjectProperties = function(hwndOwner: HWND); uFlags:
Теперь добавьте в раздел uses имя нашего модуля standardDialogs и скомпилируйте проект. Теперь можете запустить проект и посмотреть результат.
На компакт-диске в директории \Примеры\Глава 3\File Operation вы можете увидеть пример данной программы и цветные версии рисунков.
Когда речь идет о работе программы с текстовым файлом, подразумеваются процедуры ввода данных из файла в программу или, наоборот, запись этих данных в файл программой. Для текстового файла допустима простая работа с файлом без особых дополнительных механизмов, которые применяются для работы со специализированными файлами, такими как при загрузке данных из Excel или работе программы с базой данных. Разумеется, Delphi располагает возможностями работать с файлами с использованием компонентов. Но в данной статье рассматривается механизм прямой работы с текстовым файлом без использования дополнительных компонентов.
Итак, в общем виде, работа с файлом заключается в следующих этапах:
1. подключение к файлу – связь с внешним файлом, указание режима подключения;
2. выполнение операций записи в файл или чтения из файла;
3. завершение работы с файлом.
Подключение к файлу
Для связи программы с файлом используется специальная переменная – "Файловая переменная". Объявляется она так же как и любая переменная в Delphi. Тип это переменной может быть File для типизированных (хранящих данные определенного типа) файлов, а можно указать TextFile, что будет означать тип обычного текстового файла. Объявление переменной:
В исполняемом коде программы выполняется подключение к внешнему файлу:
Команда AssignFile, выполняет связь файловой переменной с внешним файлом. Вторым параметром указывается адрес файла. Он может быть задан относительным или абсолютным. Если указать только имя файла, то программа будет пытаться обнаружить его в той же директории, где она сама и находится. Абсолютный путь указывается от корневого диска:
Использование относительной директории дает возможность не привязываться к конкретным дискам и адресам. Например:
После того как выполнено подключение, выполняется процедура, устанавливающая режим работы с файлом. В основном это режим чтения или записи. Эти режимы назначаются процедурами Reset() ( для чтения) и rewrite() (для записи):
* Для команды Rewrite() следует учитывать, что при ее выполнении, она либо создает файл, указанный в файловой переменной, либо если он уже есть перезаписывает файл заново, удаляя старый без какого-то предупреждения.
Любую из указанных команд можно использовать без команды AssignFile(). Для этого достаточно вторым параметром указать путь к файлу. Таким образом, она сразу выполнит привязку файла к файловой переменной и назначит режим работы с этим файлом:
Операции с файлами
Для чтения из файла, необходимо назначить режим чтения и использовать команду Readln(), которая будет вводить в строковую переменную по одной строке из файла. Затем с этой переменой можно выполнить необходимые действия.
Обычно для загрузки всех строк из файла используется оператор цикла. Для того, чтобы определить, что файл закончился используется функция EOF() (End Of File). Таким образом получается цикл, в котором последовательно в строковую переменную вводятся все строки файла и завершающийся после окончания фала:
Для записи, назначение режим записи в файл и командой Writeln() производится запись по строкам.
Закрытие файла
По завершении работы с файлами, особенно в случае записи в них данных, необходимо корректно завершить работу с файловой переменной. Это делается для того, чтобы сохранить все внесенные в файл изменения.
Примеры работы с текстовыми файлами в Delphi
Чтение в переменную одного значения из файла:
Загрузить все строки файла в компонент Memo:
Следует отметить, что для этой задачи проще воспользоваться командой самого компонента Memo LoadFromFile().
Записать строку в файл:
Записать в текстовый файл все строки из компонента Memo:
Как и для чтения из файла в Memo, так и здесь, имеется специальная команда:
. when altering one's mind becomes as easy as programming a computer, what does it mean to be human.
15 января 2015 г.
Не используйте относительные имена файлов
Доступ к файлу можно получить по абсолютному или относительному имени (пути).
Абсолютное ("полностью квалифицированное") имя начинается с имени диска или сервера и указывает все компоненты пути, например: " C:\Projects\TestProject\Data.txt " или " \\SERVER\Projects\TestProject\Data.txt ". Такое имя всегда однозначно указывает на файл - вне зависимости от любых внешних факторов.
Относительное имя содержит не все компоненты пути и указывает файл относительно другого каталога, имя которого в самом имени не указано, например: " Data.txt " или " ..\Data.txt ". Для определения точного положения файла недостаточно одного относительного имени, необходимо ещё имя каталога, относительно которого будет трактоваться это имя. Поэтому один и тот же относительный путь может ссылаться на разные файлы. К примеру, путь " Data.txt " ссылается на C:\Projects\TestProject\Data.txt , если текущий каталог (или каталог, относительно которого происходит разрешение имени) равен C:\Projects\TestProject , но этот же путь будет ссылаться на C:\Windows\Data.txt , если текущий каталог - C:\Windows .
Подробнее о файловых именах можно почитать здесь.
Здесь же, в этой статье, я хочу показать, что вам никогда не нужно использовать относительные имена файлов.
Очень часто начинающие программисты используют относительные пути к файлам для работы с файлами внутри папки своей программы, например:
Что не так с этим кодом?
Начинающий программист считает, что имя " input.txt " будет вычисляться относительно пути к его программе. Иными словами, если программа (.exe-файл) лежит в " C:\Projects\TestProject ", то, указав " input.txt " в Assign , мы откроем файл " C:\Projects\TestProject\input.txt ". Это попросту неверно!
- Для начала, текущий каталог при старте вашей программы задаётся не вами, а вызывающим вас процессом. Все функции запуска программы имеют строковый параметр для передачи туда имени каталога, который станет текущим для запускаемой программы. И вызывающая вас программа может передать туда всё, что угодно. Это может быть папка с вашей программой, да. Но это может быть и любая другая папка;
- Далее, к примеру, если ваша программа запускается через ярлык на рабочем столе или ярлык в меню Пуск / Программы, то текущий каталог для вашей программы указан в свойствах ярлыка. Например, сама Delphi запускается с текущим каталогом = папке с проектами (например, C:\Program Files\Borland\Delphi 7\Projects\ или даже просто C:\Projects\ ) - что, очевидно, не равно папке с программой ( C:\Program Files\Borland\Delphi 7\Bin\ );
- А если вы пишете программу, которая открывает файлы какого-то типа, то вы, вероятно, назначите свою программу для открытия таких файлов (ассоциируете тип файлов в вашей программой). Но когда пользователь дважды-щёлкнет по такому файлу, ваша программа запустится с текущим каталогом равным каталогу открываемого файла. Т.е. текущий каталог будет C:\Documents , а не C:\Projects\TestProject ;
- А если вы пишете код для службы (Win32 Service), то текущий каталог будет C:\Windows\System32 .
P.S. Кроме того, размещение файлов с данными/конфигурацией в папке с программой - крайне плохая идея, если только вы не пишете портативную (portable) программу (см. также).
Далее, есть же такие функции как GetCurrentDir и (что интереснее) SetCurrentDir . "Set" решительным образом намекает на то, что текущий каталог - вещь не фиксированная и его можно менять. Иными словами, текущий каталог не только может быть не равен каталогу программы, но и вообще может меняться в процессе выполнения программы! Действительно:
Конечно, тот факт, что текущий каталог можно менять, сам по себе ещё не означает проблему. Но посмотрите на такой код:
Что случилось? Дело в том, что внешний код (а именно - код диалога открытия файла) поменял текущий каталог. Ваш код оказался не готов к этому.
P.S. Почему вообще диалог открытия файла меняет текущий каталог? Потому что вы (= прикладные программисты) пишете код с использованием относительных имён файлов.
Иными словами, проблема состоит в том, что кто угодно может менять текущий каталог в любой момент времени. Даже если в вашем коде нет вызовов другого внешнего кода, который меняет текущий каталог, всё равно текущий каталог может быть изменён другим потоком в вашей программе. Даже если вы сами не создаёте других потоков, потоки могут быть созданы DLL, которые загружены в вашу программу. Помимо системных DLL, это могут быть DLL от любых оконных ловушек, расширителей оболочек и даже антивирусов.
Вывод? Код, который адресует файл относительным именем работает благодаря случайности, а именно: благодаря тому, что никакой другой код не изменил текущий каталог перед тем, как вы вызвали функцию доступа к файлу, передав ей относительное имя файла.
Прежде чем мы посмотрим на правильное решение, давайте сделаем обзор того, чего делать не нужно.
Многие либо первым действием в программе, либо непосредственно перед выполнением участка кода с использованием относительных имён явно меняют текущий каталог на каталог программы. Например:
Или же сохраняют/восстанавливают текущий каталог перед вызовом кода, который потенциально может менять текущий каталог, например:
Что не так с этими решениями?
Во-первых, текущий каталог, являясь глобальной переменной, может быть изменён другим потоком как раз между вызовами SetCurrentDir и Assign . Если этого не происходит, то ваш (некорректный) код работает благодаря случайности (случайность состоит в том, что этого не произошло).
Во-вторых, если вы форсированно устанавливаете свой текущий каталог, отбрасывая каталог, заданный вам вызывающим, вы можете открыть не тот файл! Например, пусть вы пишете программу-конвертер, пусть она конвертирует картинки из формата .jpg в формат .jpg, пусть вашу программу можно вызвать, передав ей имя файла в командной строке. Тогда пользователь может вызвать вас так:
(здесь C:\Documents> является приглашением командной строки, а "C:\Converter\convert.exe" "holidays.jpg" - непосредственно командной строкой).
Т.е. пользователь открыл консоль, он находится в папке C:\Documents и вызывает вас ( convert.exe ) из папки C:\Converter , передавая вам имя файла ( holidays.jpg ) параметром командной строки.
(Почти аналогичная ситуация будет если пользователь дважды-щёлкнет по файлу holidays.jpg в открытой папке C:\Documents в Проводнике - при условии, что ваша программа ассоциирована с .jpg файлами).
В этом случае ваша программа ( convert.exe ) запустится из папки C:\Converter , но текущим каталогом для неё будет C:\Documents . Если вы форсированно смените текущий каталог на папку с программой ( C:\Converter ), то не сможете открыть файл holidays.jpg , поскольку он находится в папке C:\Documents , а не C:\Converter .
В-третьих, если вы передаёте имена файлов между процессами, то текущий каталог в вашей программе никак не связан с текущим каталогом в программе, с которой вы общаетесь. Например, самый типичный случай: пусть вы пишете просмотрщик файлов, ваша программа ограничена одним экземпляром. Если вас запускают на просмотр файла, вы проверяете, не открыты ли вы уже, если да - то вы передаёте в первый экземпляр имя файла и выходите.
Имя файла вам могли передать в относительном формате (к примеру, вызвали вас из командной строки). Вы передали это имя "как есть" в первую копию своей программы. Однако текущий каталог первой копии равен неизвестно чему, он не равен текущему каталогу вашей второй копии. Как бы вы не меняли текущий каталог в первой копии программы перед попыткой доступа к файлу, который вам передали, вы никак не можете знать, каким же был текущий каталог в другой программе (вашей второй копии). И снова, ваш код, оперирующий относительными путями файлов, будет работать только благодаря случайности (в этом случае случайность состоит в том, что текущий каталог оказался одинаков в обоих экземплярах вашей программы).
Более того, когда вы меняете текущий каталог на какой-то - этот каталог открывается вашей программой. В частности, это означает, что вы не можете удалить этот каталог.
Зачем же вообще относительные пути, если они так плохи?
Ну, относительные пути нужны для человека-оператора. Они экономят время на набор текста. Действительно, вместо того, чтобы вводить длинный путь вида C:\Documents and Settings\Admin\Documents\Data from 2014\March.doc - вы можете ввести просто March.doc (конечно же, при условии, что вы "находитесь" в каталоге C:\Documents and Settings\Admin\Documents\Data from 2014\ ).
Из вышесказанного напрямую следует правильное решение. Раз относительные имена предназначены для человека, то вам (вашему коду) не следует их использовать. Всё, что вы можете сделать с относительным именем - перевести его в абсолютное. И оперировать в дальнейшем только абсолютным именем файла.
- Развернуть переменные окружения;
- Преобразовать относительное имя в абсолютное;
- Канонизировать путь, свернув '.', '..' и лишние разделители каталогов;
- Преобразовать короткий путь в длинный.
- Каждый раз, когда вы получаете имя файла из внешнего источника (командной строки, конфигурации, диалога и т.п.) - сохраняйте его в переменную с суффиксом Unsafe . Например, DocumentFileNameUnsafe := ParamStr(1) ;
- Не передавайте переменные с суффиксом Unsafe в функции открытия файлов;
- Не передавайте переменные с суффиксом Unsafe в другие программы (через IPC, командную строку и т.п.);
- Вы можете сохранять переменные с суффиксом Unsafe в файл конфигурации;
- Передайте переменную с суффиксом Unsafe в функцию нормализации и сохраните результат в переменной без суффикса Unsafe . Например, DocumentFileName := PathSearchAndQualify(DocumentFileNameUnsafe) ;
- Вы можете передавать переменные без суффикса Unsafe в функции открытия файлов и другие программы (IPC, командная строка и т.п.);
- Если вам точно известно имя файла (оно задано константой в коде) и вы знаете папку, в которой лежит файл (не обязательно константа, но хотя бы логическое размещение вида "каталог программы", "подкаталог ABC папки Application Data"), то получите путь к каталогу, затем добавьте к нему имя файла и сохраните результат в переменную без суффикса Unsafe . Если имя файла, заданное в константе, содержит '.' или '..' - выполните нормализацию перед сохранением в переменную;
- Измените текущий каталог на, скажем, C:\Windows\System32 (разумеется, путь надо задавать не константой, а получать через GetSystemDirectory ) сразу после того, как вы нормализовали все имена файлов из параметров командной строки;
- Если вы запускаете внешнюю программу, передавая ей имя файла для открытия, то задайте текущий каталог для запускаемой программы равным каталогу, содержащему открываемый файл (даже хотя вы передаёте полное имя файла);
- Используйте суффикс Dir для переменных и функций, которые хранят/возвращают путь (к каталогу) без ведомого разделителя (например, 'C:\Windows'). Используйте суффикс Path для переменных и функций, которые хранят/возвращают путь с ведомым разделителем (например, 'C:\Windows\'). Избегайте использования переменных и функций, для которых вы не знаете, будет ли в конце пути разделитель. Преобразуйте такие переменные и функции в Dir или Path с помощью ExcludeTrailingPathDelimiter и IncludeTrailingPathDelimiter соответственно, например: CurrentPath := IncludeTrailingPathDelimiter(GetCurrentFolder) . Эта семантика с Dir / Path защитит вас от неверных результатов вида ExtractFileDir(. ) + 'input.txt' = 'C:\Programinput.txt' или ExtractFilePath(. ) + '\input.txt' = 'C:\Program\\input.txt' .
- Старайтесь хранить имена каталогов с ведущим разделителем, а имена файлов - без разделителя. Например, 'C:\Windows\', но 'C:\Windows\notepad.exe';
- Если вы работаете в Unicode-версии Delphi (Delphi 2009 и выше) и хотите передать имя файла во внешний код (программу или DLL) - преобразуйте имя файла в короткое имя файла ( PathGetShortPath - см. ниже). Это увеличит шансы правильного открытия файла, если вызываемый код не поддерживает Unicode или неверно обрабатывает пробелы;
- Если вы передаёте имя файла по IPC - всегда предпочитайте Unicode-форму (используйте WideString ).
- Проведите нормализацию имён файлов, как указано в алгоритме выше, сохранив их в переменные без суффикса Unsafe ;
- Получите каталог, относительно которого вам нужно сохранять пути (каталог с программой, каталог с корневым файлом документа и т.п.). Нормализуйте его и сохраните в переменную без суффикса Unsafe ;
- Получите относительный путь для вашего пути файла из п1 относительно каталога из п2 с помощью функции PathGetRelativePath (см. ниже раздел практики). Сохраните результат в переменную с префиксом Unsafe ;
- Запишите переменную из п3 в вашу конфигурацию или документ.
Давайте посмотрим, как эти рекомендации нужно делать на примерах.
Реализация: голая Delphi
Во-первых, даже в Delphi "из коробки" есть несколько подходящих функций (некоторые функции могут отсутствовать в старых версиях Delphi):Реализация: JCL
Во-вторых, хочу заметить, что если вы используете JCL (JEDI Code Library), то весь код у вас уже есть - в файле JclFileUtils : И хотя здесь нет PathGetAbsolutePath / ExpandFileName , но эта функция тривиальна:Реализация: системные функции
В-третьих, я также предлагаю вам воспользоваться функциями системы. Заметьте, что хотя эти функции относятся к функциям Оболочки (Shell), они также относятся к т.н. группе "легковесных вспомогательных функций" (Shell Lightweight Utility Functions) и импортируются из ShlwAPI.dll , а не из ShellAPI.dll . В частности, это означает, что у них нет тяжёлых зависимостей и им не нужен COM - в отличие от высококоуровневых функций Оболочки.
И для этого я предлагаю создать отдельный модуль (File / New / Other / Unit) и сохранить его, скажем, с именем ShellFileSupport.pas . В этот модуль мы поместим весь вспомогательный код. Сложные функции мы будем импортировать из системы, а простые функции напишем сами. Вот какие функции мы реализуем:
Заметьте, что некоторые функции мы реализуем сами, поэтому они работают несколько иначе, чем системные. Кроме того, для некоторых функций мы добавляем дополнительный функционал. Всё это сделано для того, чтобы упростить использование функций. Дело в том, что эти функции несколько узко-специализированы. Например, системный PathQuoteSpaces не обрабатывает кавычки внутри строки, а PathCanonicalize не преобразует некорректные разделители каталогов. Поэтому в своих функциях мы дополнительно исправляем эти упущения.
Взять готовый модуль можно здесь, а пример работы функций посмотреть здесь.
P.S. К сожалению, Delphi не поддерживает передачу кавычек в параметрах командной строки. В этом случае функции PathQuoteSpaces и PathProcessCommand используют семантику C runtime: кавычки внутри командной строки защищаются символом '\'. Но в любом случае такие параметры нельзя будет прочитать внутри Delphi-программы, если только вы не реализуете свой собственный разбор командной строки. Но зато их может прочитать другая программа, которая использует CommandLineToArgvW .
Реализации: вывод
Так или иначе, у вас есть богатый выбор: вы можете использовать встроенные функции, свои собственные полностью реализованные функции (взяв готовые из библиотеки JCL или написав свои) или же вы можете использовать системные функции, импортировав их из ShlwAPI.dll .
Вот как может выглядеть "функция нормализации", упомянутая выше, в разделе "Правильное решение":
А вот также упомянутая функция создания относительного пути:
Здесь ARootPath - каталог с программой, документом и т.п., относительно которого нужно создать путь. ATarget - имя файла, которое нужно сохранить в конфигурацию, документ и т.п. Result - собственно результат, который нужно сохранить. Например:
Да, вам также понадобится функция, чтобы "увести" текущий каталог на безопасное место. Вот подходящий код:
Примеры кода
Открываем файл в известной папке
Неправильный код:
А вот правильный вариант этого кода: или: Где GetApplicationDataPath - ваша функция получения пути к папке Application Data (подробнее).
Читаем имя файла из командной строки
Неправильный код:
А вот правильный вариант этого кода: Здесь OpenDocument - какая-то ваша функция открытия файла (например, загрузить .jpg файл и показать его на форме). Предполагается, что вы вызовете ProcessCommandLine при запуске программы, например:
(в примере выше ProcessCommandLine удобно сделать методом главной формы)
Диалоговые окна выбора имени файла используются в процессах открытия и сохранения файла. Часто эти диалоги называют также диалогами открытия/сохранения файла, хотя на самом деле они позволяют только выбрать имя файла, а все действия по открытию или сохранению файла программируются вручную.
Компонент OpenDialog реализует диалог открытия файла. При запуске этого диалога появляется окно (см. скриншот), в котором можно выбрать имя открываемого файла. В случае успешного закрытия диалогового окна (нажатием кнопки Open) в качестве результата возвращается выбранное имя файла.
Компонент SaveDialog предлагает стандартный диалог сохранения файла, который отличается от диалога открытия файла только своим заголовком.
Основные свойства компонентов OpenDialog и SaveDialog
Далее перечислены основные свойства компонентов OpenDialog и SaveDialog.
FileName типа String — указывает имя и полный путь файла, выбранного в диалоге. Имя файла отображается в строке редактирования списка Имя файла и является результатом диалога.
Title типа String— задает заголовок окна. Если свойство Title не установлено, то по умолчанию используется заголовок Open для OpenDialog и заголовок Save — для SaveDialog.
InitiaLDir типа String— определяет каталог, содержимое которого отображается при вызове диалогового окна. Если каталог не задан, то отображается содержимое текущего каталога.
DefaultExt типа String — задает расширение, автоматически используемое в имени файла, если пользователь не указал расширение.
Filter типа String— задает маски имен файлов, отображаемых в раскрывающемся списке Тип файлов. В диалоговом окне видны имена файлов, совпадающие с указанной маской (см. скриншот) это файл с расширением jpeg). По умолчанию значением Filter является пустая строка, что соответствует отображению имен файлов всех типов.
FilterIndex типа Integer — указывает, какая из масок фильтра отображается в списке. По умолчанию свойство FilterIndex имеет значение 1 (используется первая маска).
Options
Options типа TOpenOptions — применяется для настройки параметров, управляющих внешним видом и функциональными возможностями диалога. Каждый параметр (флажок) может быть установлен или снят. Свойство Options имеет около двух десятков параметров, наиболее важные перечислены ниже:
Основные параметры свойства Options
- ofAllowMultiSelect (из списка можно выбрать одновременно более одного файла);
- ofCreatePrompt (при вводе несуществующего имени файла выдается запрос на создание файла);
- ofNoLongNames (имена файлов отображаются как короткие, не более 8 символов для имени и 3 символов для расширения);
- ofOldStyleDialog (создает диалоговое окно в стиле Windows 3.11).
Фильтр представляет собой последовательность значений, разделенных знаком |. Каждое значение состоит из описания и маски, также разделенных знаком |. Описание представляет собой обычный текст, поясняющий пользователю данную маску. Маска является шаблоном отображаемых файлов и состоит из имени и расширения. Если для одного описания приводится несколько масок, то они разделяются знаком ;.
Здесь фильтр формируется с двумя масками — маской для текстовых файлов и маской для всех файлов (под текстовыми понимаются файлы типов doc и txt).
Так как в раскрывающемся списке диалогового окна отображается только описание фильтра, то для удобства целесообразно включить в описание и маску, например, так:
Фильтр обычно формируется при проектировании приложения. Для этого из окна Инспектора объектов щелчком в области значения свойства Filter вызывается Редактор фильтра (Filter Editor).
Рабочее поле окна Редактора фильтра представляет собой таблицу, состоящую из двух столбцов с заголовками Filter Name и Filter. Значения фильтра вводятся построчно, при этом в левом столбце указывается описание фильтра, а в правом — соответствующая маска (скриншот уже был выше).
Стандартные диалоговые окна выбора имени файла для открытия или сохранения файла вызываются на экран методом Execute. Эта функция в качестве результата возвращает логическое значение, позволяющее определить, как закрыт диалог. Если пользователь в процессе диалога нажал кнопку Open или Save, то диалог считается принятым, и функция Execute возвращает значение True. Если диалог был закрыт любым другим способом, то он считается не принятым, и функция возвращает значение False.
При нажатии кнопки Button2 появляется диалоговое окно OpenDialog1 выбора имени файла для открытия. При выборе имени текстового файла его содержимое загружается в компонент Memo1. Напомним, что многострочный редактор Memo позволяет работать с текстами в коде ANSI. При отмене диалога OpenDialog1 открытие файла не происходит.
Компоненты OpenPictureDialog и SavePictureDialog вызывают стандартные диалоги открытия и сохранения графических файлов. Эти стандартные диалоги отличаются от OpenDialog и SaveDialog видом окон (см. скриншот) и установленными значениями свойства Filter.
Так, по умолчанию свойство Filter установлено для отображения графических файлов следующих форматов:
- JPEG (*.jpg);
- JPEG (*.jpg);
- растровое изображение (*.bmp);
- значок (*.ico);
При использовании диалогов OpenPictureDialog и SavePictureDialog, а также OpenDialog и SaveDialog значение свойства Filter можно установить ранее рассмотренными способами, а также с помощью функции GraphicFilter (GraphicClass: TGraphicCiass) : String.
Параметр GraphicClass принадлежит к одному из графических классов: TBitmap, TGraphic (и его потомки), TIcon, TMetafile или TJPEGimage. Для работы с классом TJPEGimage нужно подключить модуль jpeg. В качестве результата функция GraphicFilter () возвращает строку с фильтрами для указанного графического класса:
TBitmap — Bitmaps (*.bmp) |*.bmp;
TIcon—Icons (* . ico) I * . ico;
TMetafile — All (*.emf;*.wmf) |*.emf;*.wmf|Enhanced Metafiles(*.emf)| *.emf|Metafiles(*.wmf) |*.wmf;
TJPEGimage — All (*.jpg;*.jpg) | *.jpg;*.jpg| JPEG Image File (*.jpg) |*.jpg| JPEG Image File (*.jpg)| *.jpg;
TGraphic — All (*.jpg;*.jpg;*.bmp;*.ico;*.emf;*.wmf) | *.jpg;*.jpg; *.bmp;*.ico;*.emf; *.wmf|JPEG Image File (*.jpg) | *.jpg| JPEG Image File (*.jpg) |*.jpgI Bitmaps (*.bmp) | *.bmp| Icons(*.ico) |*.ico | Enhanced Metafiles(*.emf)|*.emf|Metafiles(*.wmf)I*.wmf.
Если модуль jpeg в разделе uses не указан, то фильтры, соответствующие формату JPEG, будут отсутствовать.
Например, фильтр, заданный как OpenDialog1.Filter := GraphicFilter(TGraphic); позволяет отображать имена графических файлов допустимых форматов.
Читайте также: