Если Вы уже имели возможность писать приложения с использованием Microsoft Windows Forms, то наверняка убедились, что это просто. Тем более, что эта библиотека содержит множество компонентов. Но парни из Microsoft не упустят случая испортить нам жизнь. И им снова это удалось. В Windows Forms, среди прочего, не хватает одной очень простой, но нужной вещи, а именно: всем хорошо знакомого диалога выбора папок. Точнее говоря, нужный класс есть (System::Windows::Forms::Design::FolderNameEditor::FolderBrowser), но нам с вами он недоступен. Почему недоступен? Потому что объявлен со спецификатором доступа private, то есть использоваться может только в содержащем его классе (FolderBrowser - это вложенный класс). Но этого парням из Microsoft показалось мало, и они "документировали" класс FolderBrowser, вот что написано в MSDN: "Этот тип поддерживает инфраструктуру .NET Framework и не предназначен для прямого использования в Вашем коде". Кстати, в System.Design.dll определено несколько пространств имен для "внутреннего употребления" Microsoft, там можно найти практически все функции Win32 API, и множество других, в том числе и SHBrowseForFolder. Как вам такая забота о пользователях?
Но оставим Microsoft в покое, это далеко не первый случай подобного рода в их практике. К счастью, в нашем распоряжении все-таки есть достаточно средств для самостоятельной реализации нужных классов. В данной статье я хочу показать, как это можно сделать.
Класс написан с использованием Managed Extensions for C++. В принципе, можно было бы использовать технологию PlatformInvoke и просто вызвать функцию SHBrowseForFolder или даже воспользоваться объектной моделью Internet Explorer. Но в этом случае было бы очень сложно или вообще невозможно написать свой обработчик сообщений, вызываемый через callback-механизмы. Использование же MC++ позволило достаточно просто совместить управляемый и неуправляемый код и скрыть детали реализации от пользователя.
Ниже дано краткое описание функций и типов, используемых при работе BrowseFolderDialog, а также приведен пример вызова диалога из клиента на C#.
Функция обратного вызова BrowseCallbackProc
Эта функция, несмотря на малое количество кода, выполняет очень важную роль – она позволяет связать код операционной системы (неуправляемый код низкого уровня) с нужным обработчиком в управляемом классе. Чтобы показать, как это сделано, я приведу весь код функции и прокомментирую его.
Прежде всего стоит сказать, что эта функция не является членом класса BrowseFolderDialog, так как методы управляемых классов (как статические, так и нестатические) имеют соглашение о вызовах clrcall, а нам нужно использовать stdcall. Проблема здесь в том, что мы не можем изменять соглашения о вызовах для методов управляемых классов, поэтому функция BrowseCallbackProc вынесена из класса.
Одну проблему мы решили, но возникает другой вопрос: а как же получить объект класса, для которого была вызвана эта функция? К счастью, это просто. Параметр lpData содержит нечто, что может быть преобразовано к указателю на нужный нам объект. Делается такое преобразование в два этапа:
Получаем объект типа GCHandle – в функции BrowseCallbackProc это действие производится в первой строке.
У объекта типа GCHandle есть свойство Target, которое представляет собой указатель на объект типа Object (или производный от него – в нашем случае BrowseFolderDialog). Нужный нам указатель мы получаем преобразованием типов. Это действие производится во второй строке.
Теперь, когда мы имеем указатель на объект управляемого типа, мы можем смело использовать его, как нам заблагорассудится. В данном случае я просто вызываю метод BrowseFolderDialog::BrowseCallback и передаю ему полученные от ОС аргументы. Это действие производится в третьей строке.
Передача указателя на управляемый объект в неуправляемый код
С обращением к управляемым объектам из функций обратного вызова все ясно, но как же передать указатель на нужный нам объект в неуправляемый код? В нашем случае этот вопрос можно перефразировать так: как правильно заполнить соответствующие поля структуры BROWSEINFO? Я думаю, что внимательный читатель уже догадался (или посмотрел в MSDN): опять же с помощью класса GCHandle. Посмотрим на код, приведенный ниже:
Это фрагмент кода метода BrowseFolderDialog::ShowDialog. Именно здесь происходит связывание параметров для функции обратного вызова и нашего объекта. Метод GCHandle::Alloc возвращает некий описатель нашего объекта, который может быть передан в неуправляемый код, и запрещает сборщику мусора удалять объект. Поэтому необходимо по окончании работы с описателем не забыть вызвать метод Free. Вот так просто в функции, ожидающие параметры типа LPARAM передавать указатели на управляемые объекты.
Я думаю, что в коде метода BrowseFolderDialog::ShowDialog есть еще один интересный момент: передача строк из управляемого в неуправляемый код. Делается это вызовом функции PtrToStringChars, которая возвращает указатель на строку Unicode-символов. Содержимое этой строки не может меняться.
В свою очередь, преобразование из C-строки в управляемую строку делается с помощью методов Marshal::PtrToStringXXX.
Описание класса BrowseFolderDialog
Конструкторы
BrowseFolderDialog
Default-конструктор, инициализирующий поля начальными значениями.
[MC++]
public:
BrowseFolderDialog()
[C#]
public BrowseFolderDialog()
[Visual Basic.NET]
PublicSubNew()
Методы
ShowDialog
Используется для вывода диалогового окна на экран.
public DialogResult ShowDialog(IWin32Window owner)
[Visual Basic.NET]
PublicFunction ShowDialog(owner As IWn32Window) As DialogResult
Возвращаемые значения и параметры метода показаны в таблицах ниже.
Значение
Описание
OK
Пользователь выбрал какую-либо папку и нажал кнопку OK. Имя папки представлено свойством FolderName
Cancel
Пользователь отменил операцию, нажав кнопку Отмена, или произошла ошибка
Результат
Имя параметра
Описание
Owner
Любой объект, реализующий интерфейс IWin32Window, представляющий окно верхнего уровня – владельца диалогового окна
Параметры
Свойства
FolderName
Свойство, представляющее выбранную пользователем папку. Действительно только после завершения метода ShowDialog с результатом DialogResult::OK. Это свойство доступно только для чтения.
[MC++]
public:
String* FolderName
[C#]
publicstring FolderName
[Visual Basic.NET]
PublicReadOnlyProperty FolderName AsString
InitialFolderName
Свойство, задающее первоначально выбранную папку. Можно не задавать.
[MC++]
public:
String* InitialFolderName
[C#]
publicstring InitialFolderName
[Visual Basic.NET]
PublicProperty InitialFolderName AsString
Description
Свойство, задающее некоторое описание, которое может служить в качестве инструкции для пользователя. В диалоге появляется над списком папок. Можно не задавать.
[MC++]
public:
String* Description
[C#]
publicstring Description
[Visual Basic.NET]
PublicProperty Description AsString
Root
Свойство, задающее корневую папку, с которой начинается просмотр. Можно не задавать.
[MC++]
public:
String* Root
[C#]
publicstring Root
[Visual Basic.NET]
PublicProperty Root AsString
Flags
Свойство, определяющее внешний вид и поведение диалогового окна. Можно не задавать. Значения являются комбинацией членов перечисления BrowseDialogFlags.
[MC++]
public:
BrowseDialogFlags Flags
[C#]
public BrowseDialogFlags Flags
[Visual Basic.NET]
PublicProperty Flags As BrowseDialogFlags
События
ValidateFailed
Обработчик события, генерируемого при вводе недопустимого имени папки. Событие генерируется при нажатии пользователем кнопки OK. Вызывается, только если установлены флаги BrowseDialogFlags.EditBox и BrowseDialogFlags.Validate. Если обработчик не определен, ошибка игнорируется и метод ShowDialog возвращает DialogResult::Cancel.
Строка, содержащая неверное имя папки. Используется для инициализации свойства InvalidName
Параметры
Свойства
InvalidName
Свойство, задающее имя, вызвавшее ошибку. Только для чтения.
[MC++]
public:
String* InvalidName
[C#]
public string InvalidName
[Visual Basic.NET]
PublicPropertyReadOnly InvalidName AsString
Cancel
Свойство, определяющее реакцию диалогового окна на ошибку, может меняться. Наследуется от класса CancelEventArgs. Если установлено в True– окно не будет закрыто.
[MC++]
public:
bool Cancel
[C#]
publicbool Cancel
[Visual Basic.NET]
PublicProperty Cancel AsBoolean
Перечисление BrowseDialogFlags
Это перечисление введено для удобства использования в программах на языках C# и Visual Basic.NET.
<Serializable>_
PublicDelegateSub ValidateFailedEventHandler(sender AsObject, args As ValidateFailedEventArgs)
Имя параметра
Описание
Sender
Объект-источник события – всегда типа BrowseFolderDialog
Args
Объект, содержащий аргументы события
Параметры
Пример использования
Ниже приведен пример вызова методов класса BrowseFolderDialog из клиента, написанного на C#.
using System;
. . .
using Aist.Utils;
publicclass SampleForm
{
. . .
// Обработчик события – ввод неверного имени папки//privatevoid ValidateFailedHandler(object sender, ValidateFailedEventArgs args)
{
// Сообщаем об ошибкеstring strError = "Неверное имя папки: " + args.InvalidName;
MessageBox.Show(strError, "Ошибка ");
// Не даем окну закрыться
args.Cancel = true;
}
// Обработчик нажатия на кнопку выбора папки//privatevoid BrowseBtn_Click(object sender, System.EventArgs e)
{
BrowseFolderDialog dlg = new BrowseFolderDialog();
dlg.InitialFolderName = "C:\\";
dlg.Description = "Выберите рабочий каталог для программы";
dlg.Flags = BrowseDialogFlags.ReturnOnlyFSDirs | BrowseDialogFlags.NewDialogStyle |
BrowseDialogFlags.NoNewFolderButton | BrowseDialogFlags.EditBox;
// Если мы хотим обрабатывать событие ValidateFailed, нужно установить следующий флаг
dlg.Flags |= BrowseDialogFlags.Validate;
// и назначить свой обработчик
dlg. ValidateFailed += new ValidateFailedEventHandler(ValidateFailedHandler);
// Теперь все готово для показа окна - впередif (dlg.ShowDialog(this) == DialogResult.OK)
{
// Отображаем выбранную папку в поле ввода
FolderTextBox.Text = dlg.FolderName;
}
}
. . .
}
P.S.
Код писался для .NET Framework версии 1.0, в которой не было встроенного класса System.Windows.Forms.FolderBrowserDialog.
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы
то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских
прав.