Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Visual Basic.NET »

Обсудить на форуме Обсудить на форуме

Класс-обертка для функции SHBrowseForFolder

Демонстрационное приложение - 59 KB
Исходные тексты - 6 KB

Предисловие

Если Вы уже имели возможность писать приложения с использованием 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

Эта функция, несмотря на малое количество кода, выполняет очень важную роль – она позволяет связать код операционной системы (неуправляемый код низкого уровня) с нужным обработчиком в управляемом классе. Чтобы показать, как это сделано, я приведу весь код функции и прокомментирую его.

static int CALLBACK BrowseCallbackProc(HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
  GCHandle handle = GCHandle::op_Explicit(IntPtr(lpData));
  BrowseFolderDialog* pDlg = __try_cast<BrowseFolderDialog*>(handle.Target);
  return pDlg->BrowseCallback(hWnd, uMsg, lParam);
}

Прежде всего стоит сказать, что эта функция не является членом класса BrowseFolderDialog, так как методы управляемых классов (как статические, так и нестатические) имеют соглашение о вызовах clrcall, а нам нужно использовать stdcall. Проблема здесь в том, что мы не можем изменять соглашения о вызовах для методов управляемых классов, поэтому функция BrowseCallbackProc вынесена из класса.

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

  1. Получаем объект типа GCHandle – в функции BrowseCallbackProc это действие производится в первой строке.
  2. У объекта типа GCHandle есть свойство Target, которое представляет собой указатель на объект типа Object (или производный от него – в нашем случае BrowseFolderDialog). Нужный нам указатель мы получаем преобразованием типов. Это действие производится во второй строке.

Теперь, когда мы имеем указатель на объект управляемого типа, мы можем смело использовать его, как нам заблагорассудится. В данном случае я просто вызываю метод BrowseFolderDialog::BrowseCallback и передаю ему полученные от ОС аргументы. Это действие производится в третьей строке.

 

Передача указателя на управляемый объект в неуправляемый код

С обращением к управляемым объектам из функций обратного вызова все ясно, но как же передать указатель на нужный нам объект в неуправляемый код? В нашем случае этот вопрос можно перефразировать так: как правильно заполнить соответствующие поля структуры BROWSEINFO? Я думаю, что внимательный читатель уже догадался (или посмотрел в MSDN): опять же с помощью класса GCHandle. Посмотрим на код, приведенный ниже:

BROWSEINFO info;
ZeroMemory(&info, sizeof(info));
info.lpfn = BrowseCallbackProc;
GCHandle handle = GCHandle::Alloc(this);
Info.lParam = IntPtr(handle).ToInt32();
. . .

Это фрагмент кода метода BrowseFolderDialog::ShowDialog. Именно здесь происходит связывание параметров для функции обратного вызова и нашего объекта. Метод GCHandle::Alloc возвращает некий описатель нашего объекта, который может быть передан в неуправляемый код, и запрещает сборщику мусора удалять объект. Поэтому необходимо по окончании работы с описателем не забыть вызвать метод Free. Вот так просто в функции, ожидающие параметры типа LPARAM передавать указатели на управляемые объекты.

Я думаю, что в коде метода BrowseFolderDialog::ShowDialog есть еще один интересный момент: передача строк из управляемого в неуправляемый код. Делается это вызовом функции PtrToStringChars, которая возвращает указатель на строку Unicode-символов. Содержимое этой строки не может меняться.

В свою очередь, преобразование из C-строки в управляемую строку делается с помощью методов Marshal::PtrToStringXXX.

 

Описание класса BrowseFolderDialog

Конструкторы

BrowseFolderDialog

Default-конструктор, инициализирующий поля начальными значениями.

public:
  BrowseFolderDialog()
public BrowseFolderDialog()
Public Sub New()

 

Методы

ShowDialog

Используется для вывода диалогового окна на экран.

 

public:
  DialogResult ShowDialog(IWin32Window* owner)
public DialogResult ShowDialog(IWin32Window owner)
Public Function ShowDialog(owner As IWn32Window) As DialogResult

 

Возвращаемые значения и параметры метода показаны в таблицах ниже.

Значение Описание
OK Пользователь выбрал какую-либо папку и нажал кнопку OK. Имя папки представлено свойством FolderName
Cancel Пользователь отменил операцию, нажав кнопку Отмена, или произошла ошибка
Результат

 

Имя параметра Описание
Owner Любой объект, реализующий интерфейс IWin32Window, представляющий окно верхнего уровня – владельца диалогового окна
Параметры

 

Свойства

FolderName

Свойство, представляющее выбранную пользователем папку. Действительно только после завершения метода ShowDialog с результатом DialogResult::OK. Это свойство доступно только для чтения.

 

public:
  String* FolderName
public string FolderName
Public ReadOnly Property FolderName As String

 

InitialFolderName

Свойство, задающее первоначально выбранную папку. Можно не задавать.

 

public:
  String* InitialFolderName
public string InitialFolderName
Public Property InitialFolderName As String

 

Description

Свойство, задающее некоторое описание, которое может служить в качестве инструкции для пользователя. В диалоге появляется над списком папок. Можно не задавать.

 

public:
  String* Description
public string Description
Public Property Description As String

 

Root

Свойство, задающее корневую папку, с которой начинается просмотр. Можно не задавать.

 

public:
  String* Root
public string Root
Public Property Root As String

 

Flags

Свойство, определяющее внешний вид и поведение диалогового окна. Можно не задавать. Значения являются комбинацией членов перечисления BrowseDialogFlags.

 

public:
  BrowseDialogFlags Flags
public BrowseDialogFlags Flags
Public Property Flags As BrowseDialogFlags

 

События

ValidateFailed

Обработчик события, генерируемого при вводе недопустимого имени папки. Событие генерируется при нажатии пользователем кнопки OK. Вызывается, только если установлены флаги BrowseDialogFlags.EditBox и BrowseDialogFlags.Validate. Если обработчик не определен, ошибка игнорируется и метод ShowDialog возвращает DialogResult::Cancel.

 

public:
  __event ValidateFailedEventHandler* ValidateFailed
public event ValidateFailedEventHandler ValidateFailed
Public Event ValidateFailedEventHandler ValidateFailed

 

Класс ValidateFailedEventArgs

Класс введен для передачи аргументов обработчику события ValidateFailed. Наследуется от класса CancelEventArgs.

 

Конструкторы

ValidateFailedEventArgs

 

public:
  ValidateFailedEventArgs(String* strInvalidName)
public ValidateEventArgs(string strInvalidName)
Public Sub New(strInvalidName As String)

 

Имя параметра Описание
strInvalidName Строка, содержащая неверное имя папки. Используется для инициализации свойства InvalidName
Параметры

 

Свойства

InvalidName

Свойство, задающее имя, вызвавшее ошибку. Только для чтения.

 

public:
  String* InvalidName
public string InvalidName
Public Property ReadOnly InvalidName As String

 

Cancel

Свойство, определяющее реакцию диалогового окна на ошибку, может меняться. Наследуется от класса CancelEventArgs. Если установлено в True окно не будет закрыто.

 

public:
  bool Cancel
public bool Cancel
Public Property Cancel As Boolean

 

Перечисление BrowseDialogFlags

Это перечисление введено для удобства использования в программах на языках C# и Visual Basic.NET.

 

public __value enum BrowseDialogFlags
{
  BrowseForComputer = BIF_BROWSEFORCOMPUTER,
  BrowseForPrinter = BIF_BROWSEFORPRINTER,
  BrowseIncludeFiles = BIF_BROWSEINCLUDEFILES,
  BrowseIncludeURLs = BIF_BROWSEINCLUDEURLS,
  DontGoBelowDomain = BIF_DONTGOBELOWDOMAIN,
  EditBox = BIF_EDITBOX,
  NewDialogStyle = BIF_NEWDIALOGSTYLE,
  NoNewFolderButton = BIF_NONEWFOLDERBUTTON,
  ReturnFSAncestors = BIF_RETURNFSANCESTORS,
  ReturnOnlyFSDirs = BIF_RETURNONLYFSDIRS,
  Shareable = BIF_SHAREABLE,
  StatusText = BIF_STATUSTEXT,
  UAHint = BIF_UAHINT,
  UseNewUI = BIF_USENEWUI,
  Validate = BIF_VALIDATE
};
public enum BrowseDialogFlags
Public Enum BrowseDialogFlags

 

 

Делегаты

Делегат введен для того, чтобы пользователь мог определять свои правила обработки неверных имен папок.

 

[Serializable]
public __delegate void ValidateFailedEventHandler(Object* sender, ValidateFailedEventArgs* args)
[Serializable]
public delegate void ValidateFailedEventHandler(object sender, ValidateFailedEventArgs args)
<Serializable>_
Public Delegate Sub ValidateFailedEventHandler(sender As Object, args As ValidateFailedEventArgs)

 

Имя параметра Описание
Sender Объект-источник события – всегда типа BrowseFolderDialog
Args Объект, содержащий аргументы события
Параметры

 

Пример использования

Ниже приведен пример вызова методов класса BrowseFolderDialog из клиента, написанного на C#.

using System;
  . . .
using Aist.Utils;

public class SampleForm
{
  . . .

  // Обработчик события – ввод неверного имени папки
  //
  private void ValidateFailedHandler(object sender, ValidateFailedEventArgs args)
  {
    // Сообщаем об ошибке
    string strError = "Неверное имя папки: " + args.InvalidName;
    MessageBox.Show(strError, "Ошибка ");

    // Не даем окну закрыться
    args.Cancel = true;
  }

  // Обработчик нажатия на кнопку выбора папки
  //
  private void 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.


Может пригодится:


Автор: andrew_aliphanov
Прочитано: 5814
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру