В книге представлено большое количество полезной информации, необходимой каждому разработчику приложений в среде Microsoft Access, Visual Basic и Microsoft SQL Server. В ней рассматриваются: объектная модель Microsoft Access, особенности создания модулей класса, проектирование форм и отчетов. Большое внимание уделено принципам и приемам проектирования приложений для баз данных и интегрированных решений на базе Microsoft Office XP, а также средствам отладки и оптимизации приложений. К книге прилагается компакт-диск с исходными текстами всех примеров и демонстрационные базы данных, а также множество процедур и модулей классов, которые можно использовать в собственных приложениях. Книга будет полезна как начинающим, так и профессиональным разработчикам настольных бизнес-приложений для индивидуальных пользователей и небольших рабочих групп.
Содержание
Благодарности
Об авторах
Предисловие
Введение
Глава 1. Новые возможности Access 2002
Краткая история Access
Access 2002
Средства программирования
Формы и отчеты
Доступ к данным
Работа в сетях Интернет и корпоротивных сетях
Другие усовершенствования
Особенности Microsoft Office Developer
Резюме
Глава 2. Модель событий в Access
Событий так много, а времени так мало...
Использование событий
События формы
Отменяемые события
Последовательность событий
Регистрация событий
Работа программы регистрации событий
Операции с формой
События клавиатуры
Назначение свойства KeyPreview
Отмена нажатия клавиш
Использование параметров KeyCode и Shift
Сравнение событий KeyDown и KeyUp
Повторяющиеся события клавиатуры
События мыши
Двойной щелчок мышью
События, связанные с данными
События обработки ошибок
Обязательное событие по таймеру
Неблокируемые события
Резюме
Глава 3. Использование модулей класса VBA
Зачем нужны модули класса
Создание собственных объектов
Описание сложных процессов с помощью классов
Классы упрощают разработку
Как работают модули класса
Модули класса - это форма для приготовления печенья
Экземпляры объектов - это само печенье
Простой пример: класс "текстовый файл"
Создание класса
Использование класса объектов
Применение процедур Property
Получение значений свойств с помощью процедуры Property Get
Установка значений свойств с помощью процедуры Property Let
Присваивание свойствам значений с помощью процедуры Property Set
Пример простой базы данных
Дополнительные сведения о работе с модулями класса
Перечисляемые типы
Иерархия объектов
Создание свойства Parent
Коллекции объектов
Создание собственных коллекций
Определение и применение пользовательских событий
Резюме
Глава 4. SQL Access
Где можно использовать SQL
SQL Access: основные сведения
Инструкция SELECT
Предложение SELECT
Предложение FROM
Предложение WHERE
Предложение ORDER BY
Объединение таблиц
Предикаты ALL, DISTINCTROW и DISTINCT
Предикат TOP
Объявление WITH OWNERACCESS OPTION
Агрегатирование данных
Итоговые запросы, не использующие предложение GROUP BY
Использование предложения GROUP BY
Использование предложения HAVING
Создание перекрестных запросов при помощи инструкции TRANSFORM
Запросы на объединение
Использование опции TABLE
Опция ALL
Сортировка результатов
Совместимость запросов
Подчиненные запросы
Проверка значений с помощью таблицы просмотра
Сравнение значений
Проверка наличия строк
Использование подчиненных запросов в предложении SELECT
Пример: использование подчиненного запроса для нахождения повторяющихся строк
Передача параметров в SQL
Использование внешних источников данных
Применение связанных таблиц
Применение предложения IN
Применение прямых ссылок на таблицы
Обновление данных при помощи SQL
Инструкция UPDATE
Инструкция DELETE
Инструкция INSERT INTO
Инструкция SELECT INTO
Определение данных при помощи SQL
Инструкция CREATE TABLE
Предложение CONSTRAINT
Инструкция CREATE INDEX
Инструкция ALTER TABLE
Инструкция DROP
Создание SQL-запросов к серверу
Расширения Jet 4 ANSI SQL-92
Установка режима запросов ANSI SQL-92
Дополнительные операции над таблицами в Jet 4.0
Поддержка Jet 4 представлений и хранимых процедур
Обработка транзакций в Jet 4
Защита данных в Jet 4
Различия между SQL Access, SQL-92, Jet SQL-92 и T-SQL
Резюме
Глава 5. Объекты данных ADO
ADO? А как же DAO?
Создание ссылок
Выбор библиотеки
Исследование иерархий объектов
ADO, UDA и OLE DB
Правила синтаксиса
Свойства и методы
Использование объектных переменных
Объект Connection
Ссылки на объекты
Что использовать: восклицательный знак, точку или кавычки?
Положение объекта в коллекции
Коллекции, используемые по умолчанию
Перебор объектов в коллекции
Свойства объектов
Типы свойств
Перебор свойств
Определение данных с помощью объектов ADOX
Создание объектов
Наборы записей
Знакомство с курсорами
Создание наборов записей
Непосредственный доступ к таблицам
Согласованное и несогласованное обновление данных
Место расположения курсора
Взаимное влияние свойств объекта Recordset
Метод Supports
Способы создания объектов Recordset
Перемещение по набору записей
Определение числа строк в наборе записей
Определение границ набора записей
Проверка наличия записей в наборе
Просмотр всех записей
Использование массивов для хранения данных набора записей
Создание объекта Recordset с помощью объекта Command
Поиск конкретных записей
Использование закладок
Метод Clone
Сортировка наборов записей
Фильтрация записей
Обновление наборов записей
Редактирование данных в наборах записей
Постоянные наборы записей
Использование наборов записей вместо массивов
Использование объекта Command для выполнения групповых операций
Наборы записей схем
Использование коллекций, входящих в объекты CurrentProject и CurrentData
Пример использования коллекций
Проектирование формы frmDBC
Выбор операции и типа объекта
Отображение списка объектов
Заполнение списка объектов
Получение информации об опциях, выбранных пользователем в окне Options
Формирование списка объектов
Включать ли объект в список
Добавление объекта в список
И наконец...
Использование формы frmDBC в приложении
Резюме
Глава 6. Элементы управления
Элементы управления и их использование
Код, связанный с формами: модули классов
Группировка элементов управления
Свойства стандартных элементов управления
Применение свойства Tag для создания пользовательских свойств
Свойство ControlType
Свойства ControlTipText и ShortCutMenuBar
Свойства TabStop и TabIndex
Свойство DisplayWhen
События Dirty и Undo
Управление выводом текста
Применение надписей
Свойство Parent
Текстовые поля
Перенос значений полей в новые записи
Свойство ControlSource и вычисляемые элементы управления
Элементы управления с двумя состояниями
Кнопка-выключатель
Переключатели
Флажки
Группы переключателей
Добавление элементов управления в группу переключателей
Присвоение и извлечение значений
Группы переключателей и возвращаемая ими информация
Коллекция Controls элемента управления
Списки и поля со списками
Различия между списком и полем со списком
Основные свойства списков и полей со списками
Выбор нескольких элементов списка
Особенности использования свойства LimitToList
Событие NotInList
Самораскрывающиеся поля со списком
События BeforeUpdate и AfterUpdate
Программное заполнение списка или поля со списком
Множественный выбор в списке
Как и когда используются подчиненные формы
Создание подчиненной формы
Связь родительской формы с дочерней
Извлечение вычисляемых значений из подчиненных форм
Вложенные подчиненные формы
Элемент управления Command Button
Свойства командной кнопки
События командной кнопки
Элемент управления Tab Control
Что позволяют и чего не позволяют наборы вкладок
Как работает элемент управления Tab Control
Где я?
Использование набора вкладок
Модификация вкладок во время выполнения приложения
Использование набора вкладок в качестве контейнера
Замена многостраничных форм
Замена прямоугольников и групп переключателей
Настройка стандартных значений свойств элементов управления
Использование метода DefaultControl для установки стандартных значений свойств
Программное создание элементов управления
Функции для создания форм и элементов управления
Элементы управления и Windows
Использование функции SendMessage для ограничения числа вводимых символов
Резюме
Глава 7. Разработка и применение форм
Новые режимы представления формы в Access 2002
Введение в модули класса
Управление закрытием окон
Существует ли данная форма
Загружена ли данная форма
Создание всплывающих форм
Применение всплывающих форм
Как работают наши формы-примеры
Использование форм-примеров
Создание пользовательских свойств
Использование общедоступных переменных
Использование процедур Property Let/Get/Set
Использование процедур форм в качестве методов
Фильтрация данных
Свойство Filter
Свойство FilterOn
Событие Filter
Событие ApplyFilter
Настройка фильтра по форме
Сортировка записей
Работа с данными форм
На новой записи?
Какая запись является текущей и сколько строк в наборе записей
Изменены ли данные формы
Какие записи выделены
Использование свойств Recordset и RecordsetClone
Свойство RecordsetClone
Проблема, связанная со свойством RecordsetClone
Использование свойства Recordset
Recordset или RecordsetClone?
Условное форматирование
Задание условий
Задание условий форматирования в режиме конструктора
Условное форматирование во время выполнения приложения
Обработка ошибок на уровне формы
Защита от нестандартных действий пользователя
Использование свойства Cycle
Свойство KeyPreview
Вывод нескольких экземпляров формы
Что происходит при создании нового экземпляра формы
Как создать новый экземпляр формы
Лучшее решение
Закрытие экземпляра формы
Закрытие всех экземпляров формы
Использование экземпляра формы в роли диалогового окна
Использование подчиненных форм
Вложить одну форму в другую или просто их синхронизировать?
Использование синхронизированных подчиненных форм
Загружена ли форма как подчиненная
Как связать формы самостоятельно
Создание самоотключающихся навигационных кнопок
Подчиненные формы и транзакции
Дескрипторы окон, классы окон и многодокументный интерфейс
Дескриптор окна
Классы окон
Многодокументный интерфейс
Управление формами
Демонстрация возможностей класса FormInfo
Члены класса FormInfo
Как это работает
Удаление строки заголовка формы
Сохранение и восстановление координат формы
Что дальше?
Автоматическое изменение размеров форм
Понятие разрешения экрана
Масштабирование форм при их загрузке
Использование класса FormResize
Управление масштабированием отдельных элементов
Как это работает
Программирование новых режимов представления формы
Новые представления занимают всю форму
Свойства и события формы, поддерживающие представления PivotChart и PivotTable
Обработка событий представления PivotChart
Резюме
Глава 8. Разработка отчетов
Сравнение отчетов и форм
Фильтрация отчетов
Управление сортировкой и группировкой
Свойство Section
Ссылки на разделы по их именам
Работа со свойством Section
Доступ к уровням группировки
Свойства уровня группировки
Свойства раздела, задаваемые в режиме конструктора
Свойства CanGrow и CanShrink
Свойство NewRowOrCol
Свойство ForceNewPage
Свойство KeepTogether
Свойство RepeatSection
События отчета и его разделов
События отчета
События раздела
Свойства раздела, доступные только во время выполнения
Свойства MoveLayout, NextRecord и PrintSection
Свойство FormatCount
Свойство PrintCount
Свойства WillContinue и HasContinued
Примеры использования событий и свойств отчетов и их разделов
Печать произвольного числа наклеек
Печать почтовых наклеек, начиная с заданной позиции
Вставка пустых строк
Четные и нечетные страницы
Установка номера начальной страницы
Нумерация различных элементов в отчете
Несколько примеров простых отчетов
Отчет о продажах
Телефонная книга
Компании, контакты и аппаратное обеспечение
Программное изменение отчета
Создание отчета на основе перекрестного запроса
Получение настоящих имен полей
Вычисление количества выводимых полей
Настройка надписей и текстовых полей
Наведение порядка
Несколько заключительных рекомендаций
Распространение отчетов Access
Резюме
Глава 9. Управление печатью
Выбор выводного устройства
Формирование списка установленных принтеров
Изменение принтера, используемого по умолчанию
Изменение выводного устройства
Настройка параметров печати
Изменение параметров страницы
Получение информации о возможностях принтера
Использование класса PrinterCapabilities
Демонстрация класса PrinterCapabilities
Как мне...
Как просмотреть список установленных принтеров программными средствами
Как определить, используется ли принтер по умолчанию
Как заполнить список или поле со списком перечнем установленных принтеров
Как заменить принтер, устанавливаемый по умолчанию
Как заменить выводное устройство отчета
Как изменить параметры печати формы или отчета
Как настроить параметры страницы при печати формы или отчета
Как определить возможности принтера
Резюме
Глава 10. Использование Access в качестве клиента автоматизации
Основы технологии автоматизации
Терминология
Значение технологии автоматизации
Классы объектов
Библиотеки типов: ключ к классам
Просмотр объектов в Object Browser
Создание экземпляров классов
Раннее и позднее связывание
Простой пример раннего связывания
Когда создавать объекты
Функции CreateObject и GetObject
Классы серверов автоматизации
Управление другими приложениями
Изучение объектной модели приложений
Различия в поведении приложений
Память и ресурсы
Создание интегрированных решений на базе Microsoft Office
Объектные модели Office
Пример: запись отчета в Word
Создание шаблона Word
Формирование заказа
Пример: фиксация времени в Outlook
Outlook и Exchange
Взаимодействие с Outlook
Внесение в дневник новых записей
Загрузка информации из дневника
Пример: создание презентации PowerPoint
Понятие слайда PowerPoint
Работа с элементами слайдов PowerPoint
Создание простейшей презентации
Применение эффектов
Пример: заполнение таблицы Excel
Использование существующего файла
Наш сценарий
Создание объекта на основе существующего документа
Обновление электронных таблиц и диаграмм
Использование элементов ActiveX
Регистрация элемента ActiveX
Включение элемента ActiveX в приложение
Настройка свойств элемента ActiveX
Использование событий элементов ActiveX
Использование связанных элементов ActiveX
Пример использования календаря
Использование элементов ActiveX из других продуктов
Перехват событий с помощью переменных, объявленных с WithEvents
Что значит WithEvents
Использование ключевого слова WithEvents
Перехват событий в формах
Резюме
Глава 11. Access как сервер автоматизации
Использование Access в качестве сервера автоматизации
Использование библиотеки типов Access
Обычное программирование в Access: в чем разница?
Как ведет себя Access в качестве сервера автоматизации
Специальный класс для автоматизированного управления Access
Начало сеанса автоматизации
Загрузка базы данных или проекта
Защищенное подключение
Управление видимостью окна
Определяем, загружен ли проект
Выгрузка Access
Создание отчета с помощью Access
Приложение-пример
Начало сеанса автоматизированного управления Access
Открытие базы данных
Получение списка отчетов
Предварительный просмотр отчетов
Вызов пользовательских функций из приложения-клиента автоматизации
Глобальные функции
Функции уровня формы
Разработка приложений Access, поддерживающих автоматизацию
Свойство UserControl
Главное окно Access
Как помешать пользователю закрыть базу данных
Учитывайте действия программы, требующие пользовательского ввода
Создание собственного стека ошибок
Переключение в приложение-клиент
Резюме
Глава 12. Обработка ошибок и отладка кода
Синтаксические ошибки
Ошибки времени выполнения
Активизация обработчика ошибок с помощью оператора On Error
Создание интеллектуальных обработчиков ошибок
Генерация ошибок
Упрощенная обработка ошибок
Иерархия обработчиков ошибок
Свойство On Error и событие Error
Обработка ошибок доступа к данным
Создание процедуры, сообщающей об ошибках
Реализация стека вызовов
Борьба с логическими ошибками
Методы уменьшения количества ошибок в программном коде
Устраняйте ошибки сразу
Использование комментариев
Удалять или не удалять комментарии
Организация программного кода
Модуляризация программы
Требование явного описания переменных
Старайтесь избегать переменных типа Variant
Аккуратно используйте передачу параметров по значению
Будьте осторожны с операторами Dim
Объединяйте операторы Dim в группы
Используйте как можно более узкие области определения переменных
Применение согласованных правил присвоения имен
Ваш друг - функция MsgBox
Средства отладки программ в Access
Использование окна отладки
Использование точек останова
Методы отладки
Систематизированная отладка
Трудности отладки
Восстановление состояния среды программирования после останова программы
Использование утверждений
Резюме
Глава 13. Оптимизация приложений
Оптимизация приложений Access
Как Jet выполняет запросы
Определение запроса
Компиляция запроса
Оптимизация запроса
Выполнение запроса
Как заставить Jet повторно откомпилировать и оптимизировать запрос
Преимущества технологии Rushmore
Точность статистических данных
Настройка Jet
Использование метода DAO SetOption
Свойства объекта ADO Connection, предназначенные для настройки Jet
Средства оптимизации Jet, официально не поддерживаемые Microsoft
Опция ShowPlan
Метод ISAMStats
Ускорение процессов выполнения запросов и обработки наборов записей
Повышение быстродействия форм
Ограничение набора записей формы
Повышение быстродействия полей со списками, содержащими большое количество строк
Другие способы повышения быстродействия форм
Повышение быстродействия отчетов
Оптимизация и компиляция модулей VBA
Как VBA загружает программный код
Зачем нужно компилировать код
Что сохраняется при компиляции кода
Когда следует выполнять компиляцию
Зачем нужна опция Compile On Demand
Из-за чего декомпилируется код
Как компиляция влияет на использование памяти и дискового пространства
Удаляются ли модули из памяти
Как оптимизировать использование модулей
Циклические ссылки в библиотеках
Что еще вы можете сделать
Повышение быстродействия кода: тестирование усовершенствований
Создание таймера
Получение точных результатов
Как это работает
Советы по оптимизации кода VBA
Резюме
Глава 14. Доступ к DLL и Windows API
Библиотеки динамической компоновки
Вызов DLL-процедур из VBA
Использование оператора Declare
Передача аргументов функциям DLL
Возврат строк из DLL
Использование константы vbNullString
Передача DLL данных пользовательского типа
Передача массива
Использование библиотек типов с функциями DLL
Распространение приложений, использующих библиотеки типов
Разработка классов-оболочек для DLL-функций
Использование буфера обмена
Использование класса CClipboard
Структура оператора Declare
Public против Private
Задание имени процедуры
Предложение Lib
Предложение Alias
Описание аргументов
Преобразование объявлений C в объявления VBA
Более подробные сведения о вызовах DLL-функций
Передача параметров по ссылке и по значению
Передача строк в DLL: как это происходит
Что собой представляет константа vbNullString
Unicode - ANSI - Unicode
Использование типа данных Any
Использование Err.LastDLLError
Использование функций с обратным вызовом
Выравнивание структур по границе двойных слов
Конвертирование вызовов Windows API, написанных для 16-разрядной Windows
Резюме
Глава 15. Секреты мастеров
Использование процедур
Функции для работы с файлами
Проверка существования файла
Разделение полного имени файла на элементы
Получение полного имени файла
Использование диалоговых окон Windows
Использование класса CommonDlg
Использование стандартных диалоговых окон открытия и сохранения файла
Изменение внешнего вида окна открытия или сохранения файла
Выбор цвета
Выбор шрифта
Использование класса ShellBrowse
Чтение и запись данных системного реестра
Получение информации о разделе реестра
Получение имени параметра
Чтение значения из реестра
Запись значения в реестр
Получение имени подраздела реестра
Создание нового подраздела
Эмуляция функций VBA, предназначенных для работы с реестром
Функции для управления шрифтами
Получение списка шрифтов
Получение списка размеров шрифта
Получение информации о шрифтах в ваших проектах
Использование класса ScreenInfo
Что еще может класс ScreenInfo?
Сколько места займет на экране текст, выведенный данным шрифтом
Функции для работы с объектами Access
Получение списка имен объектов
Сортировка массива строк
Получение списка объектов и их типов
Сортировка массива структур adhDBObj
Разные функции
Определение национального языка
Получение полной информации о файле
Рисунки для кнопок
Использование функции adh_accGetTBDib
Несколько интересных замечаний
Резюме
Глава 16. Создание надстроек
Библиотеки, мастера, построители
Точки входа надстроек
Доступ к данным и надстройки
Надстройки и системный реестр
Создание библиотечных баз данных
Структурирование модулей библиотечных баз данных
Использование библиотечной базы данных
Ссылки на библиотечные базы данных Access
Запрет использования циклических ссылок
Редактирование кода загруженной библиотеки
Запуск процедур с помощью метода Application.Run
Использование раздела реестра LoadOnStartup для управления загрузкой библиотек
Всегда компилируйте создаваемые библиотеки
Надстройки меню: простейшие из надстроек
Создание собственных построителей
Пример построителя
Написание функции построителя
Записи в реестре, связанные с построителями
Внесение в реестр записей для новых построителей
System Color Builder
Разработка мастеров
Функции мастеров Access
Общая структура мастера
Пример мастера форм
Способы использования таблицы состояний мастера
Главная форма мастера
Программный код главной формы
Пример главной формы мастера
Глобальные функции мастера
Создание страниц мастера
Завершение процесса
Создание форм и элементов управления
Использование таблицы Access для хранения программного кода
Загрузка мастера
Записи реестра о мастерах
Шаблон мастеров - заключение
Мастер элементов управления
Функция мастера элементов управления
Элементы системного реестра для мастера элементов управления
Мастер элементов управления ActiveX
Распространение и установка надстроек
Использование диспетчера надстроек
Создание таблицы USysRegInfo
Дополнительная информация о надстройках
Восстановление ссылок с помощью VBA
Создание MDE-файлов
Надстройки COM
Надстройки COM: за и против
Диалоговое окно надстроек COM
Интерфейс IDTExtensibility2
Коллекция COMAddIns
Создание простейшей надстройки COM
Резюме
Предметный указатель
ОТРЫВОК
Глава 15
Секреты мастеров
Функции для работы с файлами
Управление стандартными диалоговыми окнами Windows
Функции для работы со шрифтами
Чтение и запись данных реестра
Получение списков объектов базы данных
Классы для получения экранных установок Windows и сведений о файлах
В предыдущих изданиях этой книги мы рассказывали о недокументированных функциях Access, которые используются в ее мастерах (wizards). В Access 2 и Access 95 эти функции хранились в отдельных библиотеках, MSAU200.DLL и MSAU7032.DLL. В Access 97 они были перенесены прямо в исполняемый файл Access, MSACCESS.EXE, и для них были определены общедоступные точки входа, подобные вызовам Windows API. В предыдущих изданиях этой книги мы их подробно описали и рассказали, как ими пользоваться.
Однако начиная с Access 2000 ситуация изменилась. Многие из доступных ранее недокументированных функций теперь программистам не доступны, а оставшиеся, похоже, оставлены ненадолго. Поэтому данная глава уже не посвящена исключительно недокументированным функциям Access, у нее теперь несколько иные задачи.
Мы предлагаем вам ряд написанных нами функций, заменяющих те недокументированные функции Access, которые были описаны в предыдущих изданиях этой книги (не все, но большую их часть). У наших функций те же имена, параметры и возвращаемые значения, так что с их помощью вы сможете перенести в Access 2002 программный код, написанный для Access 97 и использующий ее недокументированные вызовы. Это позволит вам не зависеть от решений команды разработчиков Access.
Нами разработан ряд стандартных модулей и классов, позволяющих получать информацию о версиях файлов, пользоваться стандартными диалоговыми окнами Windows открытия и сохранения файлов, выбора цвета и шрифта, обзора папок, а также выполнять многие другие важные действия. (Иными словами, изменив исходный замысел этой главы, мы фактически превратили ее в рассказ о дополнительных функциональных возможностях, необходимых разработчику современного приложения.)
Кроме того, в этой главе описываются некоторые недокументированные процедуры, которые имелись в Access 97 и по-прежнему доступны в Access 2002; при этом их нелегко написать на VBA. Нет никакой гарантии, что эти процедуры будут поддерживаться будущими версиями Access, но если сейчас их возможности вам необходимы - пока они к вашим услугам.
Программные модули, которые мы вам предлагаем, можно разделить на семь основных категорий.
Функции для работы с файлами: проверка существования файла; выделение компонентов имени файла; определение полного пути к файлу при наличии информации о его относительном местоположении.
Интерфейсы для стандартных диалоговых окон: открытия и сохранения файла; выбора шрифта; выбора цвета; просмотра папок, принтеров и компьютеров.
Процедуры для чтения и записи данных системного реестра Windows: определение количества подразделов и параметров в указанном разделе, чтение и запись параметров и создание разделов. В дополнение мы предлагаем вам замены для встроенных процедур VBA GetSetting, SaveSetting, GetAllSettings и DeleteSetting, выполняющие чтение и запись в любой части реестра.
Функции для работы со шрифтами: получение списка доступных шрифтов и их размеров; определение высоты и ширины указанной строки, выведенной заданным шрифтом.
Функции для извлечения сведений об объектах базы данных: получение списка имен объектов или массива объектов и их типов; сортировка массива строк; сортировка массива объектов по именам или типам.
Процедуры для считывания информации о версии файла: язык, кодовая страница, номер версии, тип файла, операционная система, для которой предназначен файл, имя продукта и т. п.
Процедуры для считывания информации о параметрах экрана: размеры экрана в пикселях, количество цветов, количество твипов на пиксель, доступные экранные шрифты и их размеры; а также изменение видеоустановок программным путем.
ВНИМАНИЕ. Microsoft утверждает, что документированные функции Access не будут меняться от одной ее версии к другой. Однако к недокументированным функциям это не относится. Судьба описанных в предыдущих изданиях этой книги функций является убедительным подтверждением тому, что при использовании недокументированных технологий вы ставите крест на возможности безболезненного переноса приложения в будущие версии Access. В лучшем случае проблемы будут небольшими, но едва ли вам настолько повезет, что их не будет совсем. То, что Microsoft включила в Access 95 файл MSAU7032.DLL и определила точки входа для его функций, перенесенных в Access 97 в файл MSACCESS.EXE, не означало, что Microsoft собиралась поддерживать эти функции и дальше. Как видите, она и не стала их поддерживать. Вот поэтому почти все, что мы предлагаем вам в этой главе, основано на документированных и проверенных технологиях - мы взяли идеи старой версии этой главы в качестве отправной точки и реализовали большинство старых функций посредством Window
s API и DAO.
СОВЕТ. Предлагаемый в настоящей главе программный код вначале предназначался просто для замены старых недокументированных функций Access, о которых рассказывалось в предыдущих изданиях этой книги. Однако в ходе разработки возникали новые идеи, круг задач расширялся и глава росла. В результате мы можем предложить вам не только замены для старых функций, но и во многих случаях более удобные способы решения тех же задач с использованием модулей классов. Если у вас есть программный код, в котором задействованы только процедуры adh_acc*, описанные в издании этой книги, посвященном Access 97, их можно продолжать использовать и дальше. Однако желательно все же просмотреть эту главу на предмет более удобных способов решения тех же задач.
Использование процедур
В отличие от большинства других глав книги в последующих разделах больше рассказывается не о том, как работает предлагаемый нами программный код, а о том, как его можно использовать. Просмотрев наш демонстрационный проект, вы увидите огромное количество объявлений Windows API, констант, пользовательских типов и много очень сложного VBA-кода. Разбираться в том, как весь этот код работает, вам совершенно ни к чему. Конечно, в отдельных случаях вы можете это сделать (если вас интересуют какие-то конкретные технологии или вы хотите модифицировать предложенный код), но в целом это совершенно не обязательно. Главное, чтобы наш код был вам полезен.
В базе данных CH15.MDB содержится множество стандартных модулей и модулей класса, а также демонстрационных форм. Ни одна из форм в ваших проектах не понадобится. Это значит, что если какие-то из предложенных нами технологий потребуются в вашем приложении, в него нужно будет импортировать только соответствующие модули, но не формы. В каждом разделе мы указываем, какие модули вам потребуются для реализации описанных в нем возможностей (обычно это несколько модулей).
СОВЕТ. В программном коде этой главы очень активно используются вызовы Windows API и пользовательские классы VBA. Если вы не знакомы с этими концепциями, желательно сначала ознакомиться с соответствующими главами книги (3 и 14), особенно если у вас возникнет необходимость модифицировать предложенный нами код.
Функции для работы с файлами
В большинстве приложений Access требуется выполнять те или иные операции над файлами. В наш демонстрационный проект включено несколько полезных функций, позволяющих:
проверить, существует ли файл;
разбить полное имя файла на компоненты;
получить полный путь к файлу;
Каждая из этих функций рассматривается в отдельном разделе.
СОВЕТ. Для использования описанных в этом разделе процедур в своем проекте импортируйте в него модуль basFileHandling.
Проверка существования файла
Для проверки существования файла можно вызвать функцию adh_accFileExists. Делается это так:
fRetval = adh_accFileExists(strFileName)
Эта функция возвращает значение True, если заданный файл существует, и False в противном случае.
Например, для проверки существования файла C:\AUTOEXEC.BAT можно использовать следующий код.
If adh_accFileExists("C:\AUTOEXEC.BAT") Then
' Вы знаете, что C:\AUTOEXEC.BAT существует
End If
ПРИМЕЧАНИЕ. В версии для Access 97 эта функция возвращала 0, если файл отсутствовал, и 1, если он был найден. Мы модифицировали нашу функцию для большего соответствия стандартам VBA, чтобы она возвращала ответ в виде логического значения: True (файл существует) или False (файла нет). Это может отразиться на работе вашего старого кода при его переносе в Access 2002.
Однако если вы пишете новый код, мы советуем использовать новую функцию, adhFileExists. Функцию adh_accFileExists мы оставили только для совместимости, и она не делает ничего, кроме вызова adhFileExists.
Как это работает
Функция adhFileExists использует встроенную функцию VBA Dir, которая в случае существования указанного вами файла возвращает его имя без пути. Мы передаем этой функции все возможные атрибуты файла, чтобы она нашла файл и в том случае, если он скрытый, системный или доступен только для чтения. Если функция Dir возвращает пустую строку или в ходе ее выполнения происходит ошибка, это абсолютно точно означает, что файла не существует. Вот полный код функции adhFileExists:
Public Function adhFileExists(strName As String) As Boolean
' Из модуля basFileHandling
On Error Resume Next
Dim strTemp As String
' Ищет файл с любым набором атрибутов.
strTemp = Dir(strName, _
vbHidden Or vbSystem Or _
vbArchive Or vbReadOnly)
adhFileExists = ((Len(strTemp) > 0) And _
(Err.Number = 0))
Err.Clear
End Function
Разделение полного имени файла на элементы
Очень часто программисту нужно из полного имени файла в формате
Диск:\Путь\ИмяФайла.Расширение выделить одну или несколько его частей (диск, путь, имя файла или расширение). Эту работу делает процедура adhSplitPath. Ей передается полное имя файла и четыре строковые переменные, в которых она возвращает выделенные элементы.
ПРИМЕЧАНИЕ. Процедура adhSplitPath не проверяет, существует ли заданный вами файл. Она просто разбирает строку на составные части, разделенные символами "\" и ".". Не рассчитывайте, что она будет проверять правильность имен или существование указанных вами файлов.
В листинге 15.1 приведен пример процедуры, использующей adhSplitPath, а на рис. 15.1 показан результат работы этой процедуры.
Рис. 15.1. Процедура adhSplitPath разделяет полное имя файла на составные части
Листинг 15.1
Public Sub TestSplitPath()
' Из модуля basTestFileHandling
Dim strDrive As String
Dim strPath As String
Dim strFileName As String
Dim strExt As String
adhSplitPath "C:\Windows\System\FOO.INI", strDrive, _
strPath, strFileName, strExt
Debug.Print "========================================="
Debug.Print "Full : " & "C:\Windows\System\FOO.INI"
Debug.Print "========================================="
Debug.Print "Drive: " & strDrive
Debug.Print "Path : " & strPath
Debug.Print "File : " & strFileName
Debug.Print "Ext : " & strExt
adhSplitPath "C:\", strDrive, strPath, strFileName, strExt
Debug.Print "========================================="
Debug.Print "Full : " & "C:\"
Debug.Print "========================================="
Debug.Print "Drive: " & strDrive
Debug.Print "Path : " & strPath
Debug.Print "File : " & strFileName
Debug.Print "Ext : " & strExt
End Sub
Как это работает
Процедура adhSplitPath принимает пять параметров. Один из них, обязательный первый параметр, передается по значению (с использованием ключевого слова ByVal), а остальные необязательны и передаются по ссылке (с применением ключевого слова ByRef). Процедура не возвращает никакого значения. Точнее, она возвращает выделенные компоненты имени файла через переданные вами по ссылке четыре последних строковых параметра. Например, если вас интересует папка, в которой хранится некоторый файл, вызовите процедуру adhSplitPath следующим образом:
В этом фрагменте кода предполагается, что strFullPath
содержит полное имя файла, а strPath - это строковая
переменная, в которую должно быть помещено имя папки.
adhSplitPath strFullPath, Folder:=strPath
Debug.Print strPath
В этом случае процедура adhSplitPath, как обычно, выполнит всю работу по разбиению имени файла на части, но вернет вам только значение параметра Folder.
Если вас интересуют подробности работы нашей процедуры, обратитесь к модулю basFileHandling. (Впрочем, ничего особенного, кроме манипуляций со строками, эта процедура не делает.)
СОВЕТ. Если вы еще не пользовались функцией Split, появившейся только в Access 2000, возможно, вам захочется просмотреть код adhSplitPath, чтобы понять, как с нею работать. Эта полезная функция получает строку и символ-разделитель и возвращает массив подстрок этой строки, разделенных заданным символом. Мы использовали эту функцию для выделения элементов полного имени файла.
Получение полного имени файла
Если вы знаете относительный путь к файлу, но не знаете полного пути, для получения файла можно воспользоваться нашей функцией adhFullPath. Например, если вашим текущим каталогом является C:\WINDOWS, следующий вызов
adhFullPath("..\SAMPLES\TESTAPP.EXE")
вернет строку "C:\SAMPLES\TESTAPP.EXE" - полное имя указанного вами файла. Эта функция может быть вам полезна, скажем, в случае, когда нужно знать, в каком каталоге находится файл, для которого известен только относительный путь. Тогда вы передаете этот путь функции adhFullPath, а из возвращенного ею результата выделяете имя каталога с помощью процедуры adhSplitPath, описанной в предыдущем разделе.
ПРИМЕЧАНИЕ. Назначение нашей функции может быть не совсем очевидным. Она берет имя текущего каталога и переданное вами относительное имя файла и объединяет эту информацию, составляя полное имя файла. Ничего больше она не делает: не проверяет, существует ли ваш файл и похожа ли вообще переданная вами строка на имя файла; не проверяет, существует ли каталог, входящий в состав его относительного имени. Она просто преобразует относительный путь в абсолютный.
Как это работает
Функция adhFullPath вызывает функцию Windows API GetFullPathName. Эта функция принимает относительный путь к файлу и возвращает абсолютный. Как и большинству других функций Windows API, возвращающих строки, этой функции нужно передать строковый буфер для заполнения и длину этого буфера. Но она достаточно дружественна и в случае, если в буфере не хватает места, сообщает, какой длины оказалось полное имя файла, чтобы вы могли вызвать ее еще раз и передать ей строковый буфер нужного размера. В листинге 15.2 приведен полный код функции adhFullPath.
Листинг 15.2
Public Function adhFullPath(strName As String) _
As String
' MAX_PATH определена в Windows API.
Const MAX_PATH = 260
Dim lngLen As Long
Dim lngFilled As Long
Dim strBuffer As String
lngLen = MAX_PATH
Do
strBuffer = Space(lngLen)
lngFilled = GetFullPathName( _
strName, lngLen, strBuffer, vbNullString)
' Если буфер оказался слишком мал (а
' этого не должно случиться, поскольку
' мы задали его размер с учетом максимально
' допустимой в Windows длины пути), lngFilled
' содержит значение необходимого размера буфера.
' Тогда попробуем еще раз.
If lngFilled > lngLen Then
lngLen = lngFilled
End If
Loop Until lngFilled < lngLen
adhFullPath = Left$(strBuffer, lngFilled)
End Function
Использование диалоговых окон Windows
Для стандартизации интерфейса приложений Windows предоставляет в их распоряжение несколько своих наиболее часто используемых диалоговых окон. В частности, если вы хотите предложить пользователю открыть или сохранить файл, выбрать цвет или шрифт, то можете воспользоваться стандартными окнами Windows. В Access нет механизма для вывода этих окон, за исключением окна печати, но это всегда можно сделать с помощью Windows API.
Но у функций Windows API, предназначенных для работы с диалоговыми окнами, есть один недостаток: ими слишком сложно пользоваться. Поэтому мы разработали для них удобный класс-оболочку под названием CommonDlg и включили его в базу данных CH15.MDB вместе с несколькими примерами. Об этом классе, который вы легко можете включить в свои приложения, рассказывается в следующих разделах.
В Access есть недокументированная возможность использовать диалоговое окно открытия и сохранения файла Microsoft Office. Вы видите это окно каждый раз, когда открываете или сохраняете файл в любом из приложений Microsoft Office. С помощью процедуры из предлагаемого нами модуля basFileOpen вы сможете выводить его и сами.
ВНИМАНИЕ. Не забывайте, что недокументированные средства, какими являются функции работы с окном открытия и сохранения файла Microsoft Office, могут не поддерживаться в следующих версиях Access (как это случилось со многими процедурами, описанными в предыдущих изданиях этой книги). Мы даже предполагаем, что в следующей версии Access не останется ни одной из доступных сейчас недокументированных функций. Но пока наш код работает, и мы надеемся, что Microsoft, наконец, предоставит пользователям официальный интерфейс для доступа к своему диалоговому окну.
Кроме кода для доступа к стандартным диалоговым окнам мы включили в базу данных CH15.MDB код для вывода еще одного диалогового окна, которое, хотя и не является стандартным, требуется очень часто, - окна для выбора папки. С помощью нашего класса ShellBrowse вы сможете предоставить пользователям возможность выбрать каталог, компьютер, принтер или любую виртуальную папку.
ПРИМЕЧАНИЕ. Мы не включили в наш класс CommonDlg еще одно часто использующееся диалоговое окно - Print. Пользы от его включения в этот класс было бы немного, а наш проект и так оказался внушительных размеров. Вызвав метод RunCommand с опцией acCmdPrint, вы можете легко вывести это окно самостоятельно.
Использование класса CommonDlg
Класс CommonDlg содержит код, позволяющий легко выводить диалоговые окна сохранения и открытия файла, выбора шрифта и цвета. Этот класс предоставляет целый ряд удобных свойств и методов и берет на себя всю работу по взаимодействию с Windows API. При этом в классе широко применяются пользовательские и перечислимые типы данных. Вот четыре основных метода этого класса, служащие для вывода четырех диалоговых окон:
Метод Действие
ShowColor Выводит стандартное диалоговое окно для выбора цвета
ShowFont Выводит стандартное диалоговое окно для выбора шрифта
ShowOpen Выводит стандартное диалоговое окно для открытия файла
ShowSave Выводит стандартное диалоговое окно для сохранения файла
В простейшем случае для использования этих диалоговых окон достаточно просто создать новый объект CommonDlg и вызвать один из его методов. Вот пример открытия диалогового окна вывода шрифта:
Dim cdl As CommonDlg
Set cdl = New CommonDlg
cdl.ShowFont
Debug.Print cdl.FontName
А как насчет элемента ActiveX CommonDialog?
Если у вас есть элемент ActiveX CommonDialog, его можно использовать вместо нашего класса CommonDlg. Однако с его применением связано несколько ограничений.
Этот элемент управления должен быть помещен в форму. Если вы хотите обращаться к нему из разных мест приложения, придется либо поместить его во все формы, где он может понадобиться, либо держать форму, в которой он содержится, открытой. Класс же доступен всегда и отовсюду.
Элемент управления не позволяет задать функцию с обратным вызовом (то есть функцию, которая может вызываться, пока диалоговое окно отображается на экране). Такая функция может обеспечить расположение диалогового окна там, где вы хотите, изменить надписи его элементов управления (в случае диалогового окна открытия или сохранения файла) или отреагировать на действия пользователя в этом окне. С нашим классом все это возможно, и мы включили в базу данных пример функции с обратным вызовом, которая центрирует окно на экране.
Элемент управления не позволяет задать владельца диалогового окна. Без этой возможности трудно управлять тем, что происходит, когда пользователь нажимает на клавиши Alt+Tab, пока окно находится на экране. У нашего класса CommonDlg имеется свойство hWndOwner, позволяющее вам указать, какому окну "принадлежит" открываемое вами диалоговое окно.
Элемент управления не содержит исходного кода. Ни модифицировать его, ни дополнить новыми функциями вы не сможете. Что касается нашего класса, то он весь в вашем полном распоряжении.
Общие действия
Какое бы из диалоговых окон вам ни потребовалось, процедура его открытия одна и та же.
1.Убедитесь, что ваш проект содержит класс CommonDlg. (Если вы хотите использовать описанную ниже функцию с обратным вызовом, импортируйте в свой проект также модули basCommonDlg и basCommon.)
2.Объявите в своем приложении переменную типа CommonDlg:
Dim cdl As CommonDlg
3.Создайте объект CommonDlg и присвойте ссылку на него своей переменной:
Set cdl = New CommonDlg
4.Настройте свойства объекта CommonDlg. Хотя это и не обязательно, обычно перед открытием окна с помощью свойств OpenFlags, ColorFlags или FontFlags устанавливается несколько опций. Различные опции комбинируются с помощью оператора Or. Вот пример:
cdl.InitDir = "C:\"
cdl.OpenFlags = cdlOFNAllowMultiselect Or _
cdlOFNNoChangeDir
5.Вызовите нужный вам метод объекта CommonDlg для вывода одного из его диалоговых окон (ShowColor, ShowFont, ShowOpen или ShowSave). Выполнение вашего кода будет приостановлено до закрытия диалогового окна.
cdl.ShowOpen
6.Когда пользователь закроет окно, прочитайте информацию о результатах его действий из свойств объекта CommonDlg. Например, чтобы получить выбранное пользователем имя файла, можно написать такую строчку:
Me.txtFileName = cdl.FileName
7.Закончив работу с объектом CommonDlg, освободите занимаемую им память:
Set cdl = Nothing
СОВЕТ. Как правило, после закрытия диалогового окна вы хотите узнать, не щелкнул ли пользователь на кнопке Cancel. Для этого перед открытием диалогового окна нужно присвоить свойству CalcelError объекта CommonDlg значение True и организовать перехват сообщений об ошибках. После этого, если пользователь закроет окно щелчком на кнопке Cancel, управление будет передано вашему обработчику ошибок. Более подробно об использовании этой технологии рассказывается чуть ниже в разделе "Нажал ли пользователь кнопку Cancel?".
Теперь вам известна общая схема работы с диалоговыми окнами, и нам осталось обсудить лишь некоторые детали, о которых пойдет речь в следующих разделах. Не пожалейте времени на то, чтобы просмотреть все доступные вам опции и понять их назначение, а если хотите увидеть наш класс в действии - воспользуйтесь тестовыми формами frmTestCommonDlg и frmTestFileOpenSave.
Совместимость с существующим кодом
Для того чтобы была возможность применять уже имеющийся код, в котором использовался элемент управления ActiveX Common Dialog, мы сделали класс CommonDlg совместимым с элементами ActiveX. Теперь, если у вас есть код, использующий элемент управления ActiveX, вы можете просто удалить этот элемент из вашего проекта и применить вместо него класс CopmmonDlg. Если в вашем коде был использован метод ShowOpen, ShowSave, ShowColor или ShowFont, то при работе с классом CopmmonDlg его функциональность не изменится.
Установка опций
Кроме базовых свойств, о которых рассказывается в следующих разделах, у класса CommonDlg есть еще по одному специальному свойству для каждого типа окна. Это свойство позволяет задать набор опций, управляющих содержимым и поведением окна. Класс CommonDlg передает Windows API структуру данных, один из элементов которой называется Flags. Это длинное целое, представляющее собой набор из 32 однобитовых флагов. Изменяя состояние отдельных битов, вы указываете Windows, как должно вести себя диалоговое окно.
Мы решили включить в класс CommonDlg аналогичное свойство для каждого из диалоговых окон (Open, Save, Color и Font), по своему интерпретирующего биты свойства Flags. Для каждого из окон мы определили также свою группу констант, служащих для установки его специфических опций. В модуле класса CommonDlg вы найдете перечислимые типы adhFileOpenConstants (для окон открытия и сохранения файла), adhColorConstants (для окна выбора цвета) и adhFontsConstants (для окна выбора шрифта). Соответствующие свойства называются OpenFlags, ColorFlags или FontFlags. Задавать значения опций в этих свойствах вам будет очень удобно - их можно просто выбирать в списке, предлагаемом функцией редактора VBA IntelliSense, как показано на рис. 15.2. Внутренний код класса CommonDlg объединит все три свойства (OpenFlags, ColorFlags и FontFlags) в единое свойство Flags, которое будет передано функции Windows API.
Такая организация данных значительно облегчает настройку опций диалоговых окон, так что, с одной стороны, сохраняется гибкость такого инструмента, как набор флагов, а с другой стороны, благодаря тому что флаги разделены на группы по назначению, их можно просто выбирать в списке. Вот что, к примеру, представляет собой набор флагов для настройки диалоговых окон открытия и сохранения файла (здесь важны имена констант, а не их значения):
Public Enum adhFileOpenConstants
cdlOFNAllowMultiselect = 512
cdlOFNCreatePrompt = 8192
cdlOFNEnableHook = 32
cdlOFNEnableSizing = 8388608
cdlOFNExplorer = 524288
cdlOFNExtensionDifferent = 1024
cdlOFNFileMustExist = 4096
cdlOFNHelpButton = 16
cdlOFNHideReadOnly = 4
cdlOFNLongNames = 2097152*
cdlOFNNoChangeDir = 8
cdlOFNNoDereferenceLinks = 1048576
cdlOFNNoLongNames = 262144
cdlOFNNoNetworkButton = 131072
cdlOFNNoReadOnlyReturn = 32768
cdlOFNNoValidate = 256
cdlOFNOverwritePrompt = 2
cdlOFNPathMustExist = 2048
cdlOFNReadOnly = 1
cdlOFNShareAware = 16384
End Enum
Для объединения нескольких из этих флагов в одном свойстве можно пользоваться либо оператором "+", либо оператором Or. С математической точки зрения, они делают одно и то же. На наш взгляд, выражение с операторами Or выглядит понятнее, но вообще-то это дело вкуса. Для конечного результата все равно, каким из двух операторов вы воспользуетесь. На рис. 15.2 показано, как формируется значение свойства OpenFlags.
СОВЕТ. В разделах, посвященных отдельным диалоговым окнам, приведены таблицы с описанием всех возможных значений свойства Flags.
Использование функции с обратным вызовом
Класс CommonDlg предоставляет вам одну очень важную возможность: пока открытое вами диалоговое окно остается на экране, Windows может вызвать для выполнения ваш собственный программный код. Наш пример использования этой возможности очень прост: функция с обратным вызовом центрирует диалоговое окно на экране. Однако на самом деле это очень мощное и потенциально опасное средство работы, требующее тщательного изучения документации Windows API.
ПРИМЕЧАНИЕ. Более подробно об использовании функций с обратным вызовом рассказывалось в главе 14.
Для использования функции с обратным вызовом по отношению к стандартному диалоговому окну Windows вам нужно знать ответы на следующие вопросы.
Как указать объекту CommonDlg, что Windows должна вызывать вашу функцию?
Как объявить функцию, чтобы Windows могла правильно передать ей информацию?
Как присвоить адрес функции с обратным вызовом соответствующему свойству объекта CommonDlg?
Что вы намерены делать внутри функции с обратным вызовом?
Первый вопрос самый простой: установите соответствующий флаг (имя нужной вам константы содержит фрагмент "EnableHook"). Если не установить флаг...EnableHook в свойстве...Flags, Windows никогда не вызовет вашу функцию.
Ну а как должны быть определены параметры функции с обратным вызовом? Прежде всего, помните, что типы параметров и возвращаемого вашей функцией значения и само это значение должны строго соответствовать требованиям Windows. Поскольку Windows будет вызывать вашу функцию непосредственно, без какого-либо вмешательства VBA, любые ошибки в ее объявлении приведут к разрушению приложения. Вот как должна быть объявлена функция с обратным вызовом для стандартных диалоговых окон Windows:
Public Function SampleCallback( _
ByVal hWnd As Long, ByVal uiMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Имя этой функции значения не имеет, так же как и имена ее параметров, но их типы, способ передачи (ByVal) и тип возвращаемого значения должны быть в точности такими, как показано выше. Поэтому мы предлагаем вам всегда использовать в качестве шаблона наш пример функции с обратным вызовом - тогда вы уж точно не ошибетесь.
Как же сообщить Windows, какую функцию она должна вызывать? Для этого у объекта CommonDlg есть свойство CallBack. Вы должны присвоить ему адрес своей функции, для чего используется модификатор AddressOf. (Раньше этот модификатор был доступен только в Visual Basic, теперь же им можно пользоваться во всех приложениях Office, поддерживающих VBA.) Модификатор AddressOf заменяет имя процедуры ее адресом в памяти. Чтобы этот механизм сработал, процедура должна быть объявлена как Public в стандартном модуле - она не может быть Private и не может находиться в модуле класса. (Более подробная информация о применении модификатора AddressOf приводилась в главе 14.) Однако остается одна маленькая проблема: модификатор AddressOf может использоваться только в вызове процедуры. Нельзя просто взять и написать вот так:
cdl.CallBack = AddressOf SampleCallback
VBA просто откажется компилировать такой код. И все же вам нужно как-то поместить в свойство CallBack адрес своей функции. Для этого можно воспользоваться небольшим обходным маневром. В модуле basCommon вы найдете функцию adhFnPtrToLong, которая получает один параметр типа Long и тут же возвращает его обратно:
Public Function adhFnPtrToLong(lngAddress As Long) As Long
adhFnPtrToLong = lngAddress
End Function
Чем это нам поможет? Хотя кажется, что функция adhFnPtrToLong просто ничего не делает, ей можно передать имя другой функции с модификатором AddressOf, и она вернет адрес этой функции. С помощью adhFnPtrToLong вы можете легко присвоить свойству CallBack адрес своей функции с обратным вызовом:
cdl.CallBack = adhFnPtrToLong(AddressOf SampleCallback)
Именно так мы организовывали обратные вызовы во всех примерах использования класса CommonDlg.
Теперь посмотрим, что можно делать в самой функции с обратным вызовом. Прежде всего, можно обрабатывать сообщения, переданные ей Windows. Эти сообщения указывают текущее состояние диалогового окна и позволяют принимать решения о дальнейших действиях программы. (Сообщения Windows можно рассматривать просто как константы - собственно, это и есть константы типа Long, которые Windows использует для взаимодействия между приложениями.) Windows передает вашей процедуре сообщение и дескриптор диалогового окна. Дескриптор окна - вещь полезная. С его помощью опытный программист, хорошо знающий Windows API, может проделывать с окном самые разнообразные манипуляции. Наша же простенькая функция-пример дожидается сообщения WM_INITDIALOG (указывающего, что процесс инициализации диалогового окна завершен) и центрирует окно на экране. В листинге 15.3 приведен код функции с обратным вызовом для окон выбора шрифта и цвета. (Вы найдете ее вместе с процедурой CenterWindow в модуле basCommonDlg.)
СОВЕТ. За дополнительной информацией об использовании функций с обратным вызовом по отношению к стандартным диалоговым окнам Windows вам нужно обратиться к какой-нибудь хорошей документации по Windows API. Если у вас есть подписка на Microsoft Developer Network (MSDN), начните с нее.
Листинг 15.3
Public Function CDCallback( _
ByVal hWnd As Long, ByVal uiMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case uiMsg
Case WM_INITDIALOG
' После инициализации центрируем диалоговое окно.
Call CenterWindow(hWnd)
' Вы можете получить и множество других сообщений.
' Все обычные оконные сообщения пройдут через вашу
' функцию, и вы сможете отреагировать на любые из них.
End Select
' Пусть и Windows обработает это сообщение
' стандартным образом, иначе могут возникнуть проблемы.
' Чтобы попросить ее об этом, верните 0.
CDCallback = 0
End Function
Использование стандартных диалоговых окон открытия и сохранения файла
Диалоговые окна Windows, предназначенные для открытия и сохранения файла, дают вам возможность предоставить пользователю стандартный интерфейс для выполнения этих повседневных операций. Для этого вам нужно передать Windows некоторую информацию, после чего она сама выполнит все необходимое и вернет результат.
Какие бы опции ни выбрал пользователь, какой бы файл он ни указал, само по себе диалоговое окно не будет выполнять никаких действий по открытию или сохранению файла - оно просто передаст вашему приложению информацию о действиях пользователя. А уж что делать с этой информацией, полностью зависит от вас.
Прежде чем выводить на экран диалоговое окно для открытия или сохранения файла, вы можете захотеть настроить ряд свойств объекта CommonDlg, управляющих внешним видом и поведением этого окна. Все эти свойства перечислены в табл. 15.1.
ПРИМЕЧАНИЕ. В Windows 98 и Windows 2000 можно изменить размер и координаты диалогового окна открытия или сохранения файла, и Windows их запомнит для последующего использования с этим же окном. В табл. 15.2 описаны специальные флаги, управляющие этими действиями. Мы заметили, что после ошибки VBA (например, если пользователь щелкнул на кнопке Cancel, а вы присвоили свойству CancelError значение True) Windows, похоже, "забывает" размер и координаты окна.
Таблица 15.1. Свойства класса CommonDlg, относящиеся к диалоговым окнам открытия
и сохранения файла
Свойство Тип данных Описание
CallBack Long Адрес функции с обратным вызовом, которую Windows будет вызывать в ходе работы с диалоговым окном. Эта функция может позиционировать окно или выполнять любые другие действия в зависимости от конкретной ситуации
CancelError Boolean Если присвоить этому свойству значение True, щелчок на кнопке Cancel в диалоговом окне вызовет ошибку выполнения кода класса. В обработчике ошибок проверьте, имеет ли ошибка код cdlCancel - он и означает щелчок на кнопке Cancel
DefaultExt String Если вы укажете в этом свойстве расширение, оно будет добавлено к имени выбранного пользователем файла. См. описание константы cdlOFNExtensionDifferent в табл. 15.2
DialogTitle String Текст заголовка диалогового окна
FileExtOffset Long После возвращения из ShowOpen или ShowSave содержит смещение расширения имени файла (в символах). Это облегчает разделение имени файла на составные части
FileList String() Если вы установили флаг cdlOFNAllowMultiselect, пользователь может выбрать несколько файлов. После закрытия диалогового окна этот массив строк будет содержать по одному элементу для каждого выбранного файла. FileList(0) всегда содержит имя папки, в которой находятся все выбранные файлы, а элементы от FileList(1) до FileList(n) (где n - общее количество выбранных файлов) содержат их имена. Если пользователь выбрал только один файл, FileList(0) всегда будет содержать имя его папки, а FileList(1) - имя самого файла
FileName String Полное имя выбранного файла, включая путь. Если задать это свойство до обращения к ShowOpen или ShowSave, диалоговое окно выведет указанное вами имя файла как выбранное по умолчанию
FileNameBufferSize Long Перед тем как попросить Windows вывести окно открытия или сохранения файла, класс CommonDlg должен выделить память для возвращаемых имен файлов. По умолчанию выделяется 20 000 байт. Если вам кажется, что этого мало, можете задать в данном свойстве другой размер буфера. Однако если вы точно знаете, что пользователи не станут выбирать много файлов за один раз, значения по умолчанию будет вполне достаточно
FileOffset Long После возвращения из ShowOpen или ShowSave это свойство содержит смещение в символах имени файла относительно начала его полного пути
FileTtile String После возвращения из ShowOpen или ShowSave это свойство содержит имя выбранного файла и расширение без пути. Если выбрано несколько файлов, это свойство останется пустым
Filter String Список предлагаемых пользователю фильтров в виде пар имя фильтра/шаблоны, разделенных символами "
FilterIndex Integer Индекс фильтра (начиная с 1), который нужно выбрать при открытии окна. По умолчанию выбирается первый фильтр. После закрытия окна содержит индекс выбранного пользователем фильтра
Flags Long Ноль или более значений из табл. 15.2 (скомбинированных с помощью оператора Or), указывающих, как должно быть инициализировано диалоговое окно и как оно должно себя вести. Вы можете задать либо это значение, либо значение свойства OpenFlags. Последнее дает вам возможность выбирать отдельные значения в списке констант adhFileOpenConstants, поэтому мы предлагаем всегда пользоваться свойством OpenFlags, а не свойством Flags, которое включено в класс для совместимости с элементом ActiveX CommonDialog
hWndOwner Long Дескриптор окна, родительского по отношению к открываемому диалоговому окну открытия или сохранения файла. Если вы открываете названное окно из формы, присвойте этому свойству значение свойства hWnd формы, в противном случае - значение Application.hWndAccessApp
InitDir String Папка, файлы которой диалоговое окно должно показать после открытия. Если не задать это свойство, в диалоговом окне будут выведены файлы текущей папки. После того как пользователь сделает выбор, Windows назначит выбранную им папку текущей, если только вы не установите флаг cdlOFNNoChangeDir
OpenFlags AdhFileOpen-Constants Ноль или более значений из табл. 15.2 (скомбинированных с помощью оператора Or), указывающих, как должно быть инициализировано диалоговое окно и как оно должно себя вести. Вы можете задать либо это значение, либо значение свойства Flags. Первое дает вам возможность выбирать отдельные флаги из списка констант adhFileOpenConstants, поэтому мы предлагаем всегда пользоваться свойством OpenFlags, а не свойством Flags, которое включено в класс для совместимости с элементом ActiveX CommonDialog
Таблица 15.2. Допустимые значения свойств Flags и OpenFlags
Константа Описание
cdlOFNAllowMultiselect Указывает, что пользователь может выбрать в списке более одного файла. Если вы установили этот флаг, используйте для работы с массивом полученных имен свойство FileList (см. табл. 15.1)
cdlOFNCreatePrompt Если этот флаг установлен и пользователь указал несуществующий файл, Windows запросит у него подтверждение необходимости его создания. Однако Windows на самом деле не будет создавать файл, а просто закроет окно, и свойство FileName будет содержать указанное пользователем имя файла. Если же флаг не установлен и пользователь указал несуществующий файл, Windows просто закроет окно. Если этот флаг используется в сочетании с флагом cdlOFNAllowMultiselect, допускается задание только одного несуществующего файла
cdlOFNEnableHook Если этот флаг установлен, в ходе работы с окном Windows будет вызывать вашу функцию, заданную в свойстве CallBack. В Windows 98, Windows 2000 и Windows XP, если установить этот флаг и не установить флаг cdlOFNEnableSizing, пользователь не сможет менять размер диалогового окна
cdlOFNEnableSizing (Только в Windows 98, Windows 2000 и Windows XP.) По умолчанию пользователь может менять размер диалогового окна, и Windows запоминает установленную им позицию и размер, используя их при следующем открытии окна. Как правило, этот флаг вам не нужен, но если вы установили флаг cdlOFNEnableHook, Windows считает, что вы хотите сами управлять размером и координатами окна, и запрещает делать это пользователю. Однако с помощью нашего класса вы не можете позиционировать окно, и поэтому, если вы устанавливаете флаг cdlOFNEnableHook, установите и cdlOFNEnableSizing. В Windows 95 и Windows NT данный флаг игнорируется, поэтому вы не сможете изменять размер диалогового окна
cdlOFNExplorer Указывает Windows, что диалоговое окно должно иметь интерфейс Windows Explorer. Класс CommonDlg всегда устанавливает этот флаг. Если вас это не устраивает, придется модифицировать код нашего класса
cdlOFNExtensionDifferent По возвращении из диалогового окна указывает, что пользователь выбрал файл с расширением, отличающимся от заданного в свойстве DefaultExt. Если вы не задали значение свойства DefaultExt, этот флаг никогда не будет установлен. Для выяснения значения этого флага используйте следующий код:If cldOpenFlags And _ cdlOFNExtensionDifferent <> 0 Then ' Вы знаете, что выбран файл с ' расширением, отличающимся от ' заданного вами в свойстве DefaultExtEnd If
cdlOFNFileMustExist Указывает, что пользователь может вводить имена только существующих файлов. Если пользователь введет имя несуществующего файла, Windows выведет сообщение об ошибке. Когда данный флаг установлен, диалоговое окно ведет себя так, как будто установлен также и флаг cdlOFNPathMustExist
cdlOFNHelpButton Управляет выводом в диалоговом окне кнопки Help. Хотя вы вполне можете реагировать на щелчок пользователя на этой кнопке, для этого нужно создать подкласс окна и обрабатывать зарегистрированные сообщения Windows. Рассказ об этом выходит за рамки нашей книги, и мы включили в наш класс данную константу только для совместимости с элементом ActiveX CommonDialog1
cdlOFNHideReadOnly Если этот флаг установлен, в диалоговом окне отсутствует опция Read Only
cdlOFNLongNames Для диалоговых окон старого стиля (см. описание флага cdlOFNExplorer) разрешает использование длинных имен файлов. Этот флаг не используется классом CommonDlg и включен в него только для совместимости
cdlOFNNoChangeDir Если этот флаг установлен, то после того, как пользователь закроет диалоговое окно, Windows восстановит исходный текущий каталог, даже если пользователь выбрал файлы из другого каталога
cdlOFNNoDereferenceLinks Если пользователь выберет ярлык (LNK-файл), Windows вернет вам имя и путь этого LNK-файла. Если же этот флаг не установлен, Windows вернет имя и путь того файла, на который ссылается выбранный ярлык
cdlOFNNoLongNames Для диалоговых окон старого стиля (см. флаг cdlOFNExplorer) установка флага вызывает вывод всех файлов в формате 8.3. Этот флаг не используется классом CommonDlg и включен в него только для совместимости
cdlOFNNoNetworkButton Для диалоговых окон старого стиля (см. опсание флага cdlOFNExplorer) при установке этого флага из диалогового окна удаляется кнопка Network. Данный флаг не используется классом CommonDlg и включен в него только для совместимости
cdlOFNNoReadOnlyReturn Если после закрытия диалогового окна этот флаг оказывается установленным, значит, пользователь не установил флаг Read Only и сам файл не находится в папке, защищенной от записи. Чтобы проверить, установлен ли этот флаг, воспользуйтесь оператором And (см. пример для флага cdlOFNExtensionDifferent)
cdlOFNNoValidate Указывает, что в имени файла можно применять недопустимые символы. Как правило, этот флаг устанавливать нежелательно1
cdlOFNOverwritePrompt Если этот флаг установлен и файл, выбранный пользователем в диалоговом окне сохранения файла, уже существует, Windows выведет диалоговое окно для подтверждения необходимости проведения операции. Разработчик может решить, предупреждать ли пользователя о вводе имени уже существующего файла
cdlOFNPathMustExist Определяет, что пользователь может указывать только допустимые имена файлов и папок. Если этот флаг установлен, Windows выводит диалоговое окно, предупреждающее пользователя о вводе неверного пути и имени
cdlOFNReadOnly Если этот флаг установлен, по умолчанию в диалоговом окне устанавливается флаг Read Only. После закрытия диалогового окна этот же флаг указывает, оставил ли пользователь этот флаг или снял его. Чтобы проверить, установлен ли данный флаг, воспользуйтесь оператором And (см. пример для флага cdlOFNExtensionDifferent)
cdlOFNShareAware Если этот флаг установлен и пользователь выбрал файл, который уже открыт, ошибка игнорируется и Windows возвращает имя выбранного файла1
1Для полной поддержки опции требуется написание дополнительного программного кода с использованием Windows API. Поэтому мы рекомендуем использовать данный флаг, только если вы точно знаете, что делаете.
Если стандартное поведение диалогового окна открытия или сохранения файла вас устраивает, вам не нужно устанавливать никакие из описанных флагов. Вызовите метод ShowOpen или ShowSave, и стандартное диалоговое окно будет выведено на экран. Если, к примеру, выполнить следующий код, то сначала появится диалоговое окно открытия файла, а после его закрытия - окно с сообщением о том, какой файл вы в нем выбрали.
Dim cdl As CommonDlg
Set cdl = New CommonDlg
cdl.ShowOpen
MsgBox cdl.FileName
Если же вы хотите указать Windows, какие файлы следует предлагать пользователю, какую папку открыть первой, выводить ли в окне флажок Read Only и т. п., тогда вам нужно изучить свойства и флаги, описанные в табл. 15.1 и 15.2. В этом вам помогут демонстрационные формы frmTestCommonDlg и frmTestFileOpenSave. В листинге 15.4 приведена процедура из модуля формы frmTestCommonDlg, в которой используются многие свойства класса CommonDlg.
СОВЕТ. Для выполнения этой процедуры откройте форму frmTestCommonDlg, щелкните в ней на кнопке Test File Open и выберите файл. Если вы выберете файл с расширением, отличным от BAT, то получите предупреждающее сообщение о том, что выбрали файл не с тем расширением.
Процедура cmdFileOpen_Click выполняет следующие действия.
Объявляет и создает объект CommonDlg:
Dim cdl As CommonDlg
Set cdl = New CommonDlg
Делает текущую форму владельцем диалогового окна:
cdl.hWndOwner = Me.hWnd
Задает набор фильтров для отбора файлов и указывает, какой из них нужно применить по умолчанию:
' Устанавливаем три пары значений для фильтрации файлов.
cdl.Filter = _
"Text files (*.txt)|" & _
"*.txt|" & _
"Database files (*.mdb, *.mde, *.mda)|" & _
"*.mdb;*.mde;*.mda|" & _
"All files (*.*)|" & _
"*.*"
' Выбираем второй фильтр (Database files) для
' отбора файлов при открытии диалогового окна.
cdl.FilterIndex = 2
Устанавливает свойство OpenFlags, указывая, как должно вести себя диалоговое окно:
cdl.OpenFlags = cdlOFNEnableHook Or _
cdlOFNNoChangeDir Or cdlOFNFileMustExist
Назначает функцию с обратным вызовом, для чего присваивает свойству CallBack адрес нашей функции-примера из модуля basCommonDlg. Функция с обратным вызовом должна находиться в стандартном модуле, быть объявлена как Public и соответствовать требованиям, перечисленным в разделе "Использование функции с обратным вызовом". Для записи адреса процедуры в переменную необходимо вызвать функцию adhFnPtrToLong:
cdl.CallBack = adhFnPtrToLong(AddressOf GFNCallback)
Устанавливает еще несколько свойств:
cdl.InitDir = "C:\"
cdl.FileName = "autoexec.bat"
cdl.DefaultExt = "bat"
Вызывает метод ShowOpen, после чего выполнение кода приостанавливается до тех пор, пока диалоговое окно не будет закрыто:
cdl.ShowOpen
После закрытия диалогового окна считывает выбранное пользователем имя файла:
txtFileOpen = cdl.FileName
Проверяет, имеет ли имя файла то расширение, которое вы задали в свойстве DefaultExt:
If (cdl.OpenFlags And _
cdlOFNExtensionDifferent) <> 0 Then
MsgBox "You chose a different extension!"
End If
В листинге 15.4 эта процедура приведена целиком.
СОВЕТ. Чтобы эта процедура выполнялась в вашем приложении, нужно импортировать в него модуль нашего класса CommonDlg. Если вы хотите использовать функцию с обратным вызовом, импортируйте также модули basCommon и basCommonDlg (содержащие ее образец и функцию, необходимую для организации ее вызова).
СОВЕТ. Об использовании свойства CancelError для перехвата ошибки, генерируемой, когда пользователь закрывает окно кнопкой Cancel, рассказывается в следующем разделе.
Листинг 15.4
Private Sub cmdFileOpen_Click()
' Тестирует диалоговое окно открытия файла и класс CommonDlg.
Dim cdl As CommonDlg
Set cdl = New CommonDlg
cdl.hWndOwner = Me.hWnd
cdl.CancelError = True
On Error GoTo HandleErrors
' Задаем три пары значений для фильтрации файлов.
cdl.Filter = _
"Text files (*.txt)|" & _
"*.txt|" & _
"Database files (*.mdb, *.mde, *.mda)|" & _
"*.mdb;*.mde;*.mda|" & _
"All files (*.*)|" & _
"*.*"
' Выбираем второй фильтр (Database files) для
' отбора файлов при открытии диалогового окна.
cdl.FilterIndex = 2
' Устанавливаем флаги, указывающие, что мы
' хотим использовать функцию с обратным вызовом,
' после закрытия окна вернуться в исходную папку,
' а также хотим, чтобы пользователь мог выбрать
' только cуществующий файл.
cdl.OpenFlags = cdlOFNEnableHook Or _
cdlOFNNoChangeDir Or cdlOFNFileMustExist
' Задаем адрес функции с обратным вызовом.
cdl.CallBack = adhFnPtrToLong(AddressOf GFNCallback)
' Устанавливаем еще несколько свойств.
cdl.InitDir = "C:\"
cdl.FileName = "autoexec.bat"
cdl.DefaultExt = "bat"
' Открываем диалоговое окно открытия файла
' и ждем, пока пользователь его закроет.
cdl.ShowOpen
' Считываем имя выбранного пользователем файла.
txtFileOpen = cdl.FileName
' Проверяем свойство OpenFlags (или Flags), чтобы
' выяснить, отличается ли расширение файла от
' заданного нами расширения по умолчанию.
If (cdl.OpenFlags And _
cdlOFNExtensionDifferent) <> 0 Then
MsgBox "You chose a different extension!"
End If
ExitHere:
Set cdl = Nothing
Exit Sub
HandleErrors:
Select Case Err.Number
Case cdlCancel
' Пользователь нажал кнопку Cancel!
Resume ExitHere
Case Else
MsgBox "Error: " & Err.Description & _
"(" & Err.Number & ")"
End Select
Resume ExitHere
End Sub
ПРИМЕЧАНИЕ. Единственным отличием окон открытия и сохранения файла является интерпретация ими некоторых из перечисленных в табл. 15.2 флагов. Эти окна выглядят и ведут себя практически одинаково. Какой бы метод вы ни собирались вызвать, ShowOpen или ShowSave, для установки нужных флагов пользуйтесь свойством OpenFlags.
СОВЕТ. Если вы предпочитаете открывать окно с помощью одного вызова процедуры, которая скрыла бы от вас подробности процесса создания объекта и установки его свойств, то можете воспользоваться функцией adhCommonFileOpenSave из модуля basFileOpen. Ей передаются параметры, определяющие поведение окна, а возвращает она имя выбранного пользователем файла.
Нажал ли пользователь кнопку Cancel?
Если вас интересует, нажал ли пользователь кнопку Cancel (а не просто щелкнул на кнопке OK, не выбрав ни одного файла), добавьте в свою процедуру дополнительный код.
1.Присвойте свойству CancelError объекта CommonDlg значение True.
2.Включите в процедуру обработчик ошибок и проверьте в нем наличие ошибки cdlCancel (32755). Эту ошибку генерирует класс CommonDlg после того, как пользователь щелкнет на кнопке Cancel (или нажмет клавишу Esc).
В обработчике события можно отреагировать на нажатие пользователем этой кнопки. Однако чаще приложение просто продолжает работу, не делая в ответ ничего особенного. Например, обработчик события cmdChooseColor_Click из модуля формы frmTestCommonDlg в случае нажатия кнопки Cancel просто ничего не делает (об использовании диалогового окна выбора цвета подробно рассказывается в специально посвященном ему разделе этой главы):
Private Sub cmdChooseColor_Click()
Dim cdl As CommonDlg
On Error GoTo HandleErrors
Set cdl = New CommonDlg
' Если пользователь нажмет кнопку Cancel,
' сгенерировать ошибку времени выполнения.
cdl.CancelError = True
' Здесь часть кода опущена.
cdl.ShowColor
' Здесь часть кода опущена.
ExitHere:
Set cdl = Nothing
Exit Sub
HandleErrors:
Select Case Err.Number
Case cdlCancel
' Ничего не делаем.
Case Else
MsgBox "Error: " & Err.Description & _
" (" & Err.Number & ")"
End Select
Resume ExitHere
End Sub
ВНИМАНИЕ. Обязательно проверьте, какая из опций перехвата ошибок выбрана для параметра VBA Error Trapping (Tools ? Options ? General). Если выбрана опция Break in Class Module, ни один из модулей класса, в которых вы перехватываете ошибку cdlCancel, не будет работать правильно. Дело в том, что эта опция заставляет VBA перейти в режим останова, как только произойдет ошибка в модуле класса. В Visual Basic, создающем независимые приложения, эта опция полезна, но в Access она может пригодиться лишь в некоторых случаях и только на время отладки. Оставлять ее в готовом приложении нельзя. Так что выберите лучше опцию Break on Unhandled Errors, иначе вы не сможете пользоваться свойством класса CancelError. Хотите убедиться? Установите опцию Break in Class Module, откройте форму frmTestCommonDlg, щелкните в ней на первой кнопке, а в диалоговом окне открытия файла - на кнопке Cancel. Вы тут же окажетесь в редакторе Visual Basic в режиме останова - выполнение кода будет остановлено в обработчике ошибки. Если вы не хотите, чтобы в такой же ситуации оказался пользователь, тогда проверяйте установку этой опции в каждом готовом проекте.
Разработка настольных приложений в Access 2002. Для профессионалов (+CD). / П. Литвин, К. Гетц, М. Гунделой - СПб: Питер, 2002. - 1008 с.
|