Диалог выбора файла или папки

В группе функций по работе с файлами и папками есть одна, позволяющая интерактивно запросить имя файла или папки, а также группу файлов у пользователя, чтобы передать эту информацию в MQL-программу. Вызов данной функций — FileSelectDialog — приводит к появлению в терминале стандартного окна выбора файлов и папок системы Windows.

Поскольку диалог прерывает выполнение MQL-программы до момента его закрытия, вызов функции разрешен только в двух типах MQL-программ, которые исполняются в отдельных потоках: экспертах и скриптах (см. раздел о типах MQL-программ). Пользоваться данной функцией запрещено в индикаторах и сервисах: первые исполняются в интерфейсном потоке терминала (и их остановка заморозила бы обновление графиков соответствующих инструментов), а вторые исполняются в фоновом режиме и не могут обращаться к пользовательскому интерфейсу.

Все элементы файловой системы, с которыми работает функция, находятся внутри "песочницы", то есть в каталоге текущей копии терминала или агента тестирования (если программа выполняется в тестере), в подпапке MQL5/Files.

При наличии флага FSD_COMMON_FOLDER в параметре flags (см. далее) используется общая "песочница" всех терминалов Users/<пользователь>...­MetaQuotes/Terminal/Common/Files.

Внешний вид диалога зависит от операционной системы Windows. Ниже показан один из возможных вариантов интерфейса.

Диалог выбора файлов и папок Windows

Диалог выбора файлов и папок Windows

int FileSelectDialog(const string caption, const string initDir, const string filter,
    uint flags, string &filenames[], const string defaultName)

Функция выводит стандартный диалог Windows для открытия или создания файла, или выбора папки. В параметре caption задается заголовок диалога: при значении NULL используется стандартный заголовок — "Открыть" для чтения или "Сохранить как" для записи файла, или же "Выбор папки", в зависимости от флагов в параметре flags.

Параметр initDir позволяет задать начальную папку, для которой откроется диалог. При значении NULL будет показано содержимое папки MQL5/Files. Эта же папка используется, если в initDir указан несуществующий путь.

С помощью параметра filter можно ограничить набор расширений файлов, которые будут показаны в диалоговом окне. Файлы других форматов будут скрыты. Значение NULL означает отсутствие ограничений.

Формат строки filter следующий:

"<описание 1>|<расширение 1>|<описание 2>|<расширение 2>..."

В качестве описания допустима любая строка. В качестве расширения можно написать любой фильтр с подстановочными символами '*' и '?', которые мы рассматривали в разделе Поиск файлов и папок. Символ '|' является разделителем.

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

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

Например, "Text documents (*.txt)|*.txt|All files (*.*)|*.*", при этом первое расширение "Text documents (*.txt)|*.txt" будет выбрано как тип файла по умолчанию.

В параметре flags можно указать с помощью оператора '|' битовую маску, задающую режимы работы. Для неё определены следующие константы:

  • FSD_WRITE_FILE — режим для записи файлов ("Сохранить как"); при отсутствии данного флага по умолчанию используется режим чтения ("Открыть"); при наличии данного флага всегда разрешен ввод произвольного нового имени, независимо от флага FSD_FILE_MUST_EXIST;
  • FSD_SELECT_FOLDER — режим выбора папки (только одной и только существующей); при данном флаге все прочие флаги кроме FSD_COMMON_FOLDER игнорируются или вызывают ошибку; запросить явным образом создание папки нельзя, но в диалоге есть возможность создать папку интерактивно и тут же её выбрать;
  • FSD_ALLOW_MULTISELECT — разрешение выбирать несколько файлов в режиме чтения; этот флаг игнорируется, если указан FSD_WRITE_FILE или FSD_SELECT_FOLDER;
  • FSD_FILE_MUST_EXIST — выбранные файлы должны существовать; если пользователь попытается указать произвольное имя, диалог выведет предупреждение и останется открытым; данный флаг игнорируется, если указан режим FSD_WRITE_FILE;
  • FSD_COMMON_FOLDER — диалог открывается для общей "песочницы" всех клиентских терминалов.

Функция заполнят массив строк filenames именами выбранных файлов или папки. Если массив динамический, его размер изменяется под фактическое количество данных, в частности, расширяется или наоборот урезается вплоть до 0, если ничего не было выбрано. Если массив фиксированный, он должен быть достаточного размера, чтобы принять ожидаемые данные. В противном случае возникнет ошибка 4007 (ARRAY_RESIZE_ERROR).

Параметр defaultName указывает имя файла/папки по умолчанию, которое будет подставлено в соответствующее поле ввода сразу после открытия диалога. Если параметр равен NULL, поле будет изначально пустым.

Если параметр defaultName задан, то во время невизуального тестирования MQL-программы вызов FileSelectDialog вернёт 1, а само значение defaultName будет скопировано в массив filenames.

Функция возвращает количество выбранных элементов (0, если пользователь ничего не выбрал) или -1 в случае ошибки.

Рассмотрим примеры работы функции в скрипте FileSelect.mq5. В функции OnStart будем последовательно вызывать FileSelectDialog с разными настройками. Пока пользователь выбирает что-то (не нажимает кнопку "Отмена" в диалоге), тест продолжается вплоть до последнего шага (даже если функция выполнилась с кодом ошибки).

void OnStart()
{
   string filenames[]; // динамический массив подойдет для любого вызова
   string fixed[1]; // слишком маленький массив, если файлов больше 1
   const string filter = // пример фильтров
      "Text documents (*.txt)|*.txt"
      "|Files with short names|????.*"
      "|All files (*.*)|*.*";

Сперва запросим у пользователя один файл из папки "MQL5Book". Можно выбрать существующий файл или ввести новое имя (потому что нет флага FSD_FILE_MUST_EXIST).

   Print("Open a file");
   if(PRTF(FileSelectDialog(NULL"MQL5book"filter,
      0filenamesNULL)) == 0return;             // 1
   ArrayPrint(filenames);                            // "MQL5Book\utf8.txt"

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

Затем сделаем аналогичный запрос в режиме "для записи" (с флагом FSD_WRITE_FILE).

   Print("Save as a file");
   if(PRTF(FileSelectDialog(NULL"MQL5book"NULL,
      FSD_WRITE_FILEfilenamesNULL)) == 0return;// 1 
   ArrayPrint(filenames);                            // "MQL5Book\newfile"

Здесь пользователь также сможет выбрать как существующий файл, так и ввести новое имя. Проверку на то, что пользователь собирается перезаписать существующий файл, должен сделать программист (диалог предупреждений не выдает).

Теперь проверим выбор нескольких файлов (FSD_ALLOW_MULTISELECT) в динамический массив.

   if(PRTF(FileSelectDialog(NULL"MQL5book"NULL,
     FSD_FILE_MUST_EXIST | FSD_ALLOW_MULTISELECTfilenamesNULL)) == 0return// 5
   ArrayPrint(filenames);
   // "MQL5Book\ansi1252.txt" "MQL5Book\unicode1.txt" "MQL5Book\unicode2.txt"
   // "MQL5Book\unicode3.txt" "MQL5Book\utf8.txt"

Наличие флага FSD_FILE_MUST_EXIST означает, что диалог выведет предупреждение и останется открытым, если попытаться ввести новое имя.

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

   Print("Open multiple files (fixed, choose more than 1 file for error)");
   if(PRTF(FileSelectDialog(NULL"MQL5book"NULL,
      FSD_FILE_MUST_EXIST | FSD_ALLOW_MULTISELECTfixedNULL)) == 0return;
   // -1 / ARRAY_RESIZE_ERROR(4007)
   ArrayPrint(fixed); // null

Наконец, протестируем работу с папками (FSD_SELECT_FOLDER).

   Print("Select a folder");
   if(PRTF(FileSelectDialog(NULL"MQL5book/nonexistent"NULL,
      FSD_SELECT_FOLDERfilenamesNULL)) == 0return// 1
   ArrayPrint(filenames); // "MQL5Book"

В данном случае в качестве стартового пути указана несуществующая вложенная папка "nonexistent", поэтому диалог откроется в корне "песочницы" — MQL5/Files. Там мы выбрали "MQL5book".

Если скомбинировать некорректное сочетание флагов, получим еще одну ошибку.

   if(PRTF(FileSelectDialog(NULL"MQL5book"NULL,
      FSD_SELECT_FOLDER | FSD_WRITE_FILEfilenamesNULL)) == 0return;
   // -1 / INTERNAL_ERROR(4001)
   ArrayPrint(filenames); // "MQL5Book"
}

Из-за ошибки функция не стала модифицировать переданный массив, и в нем остался прежний элемент "MQL5Book".

В данном тесте мы намеренно проверяли результаты только на 0, чтобы продемонстрировать все варианты вне зависимости от наличия ошибок. В реальной программе проверяйте результат функции с учетом ошибок, т.е. с условиями на три исхода: выбор сделан (>0), выбор не сделан (==0) и ошибка (<0).