Программисту-профессионалу
Времена меняются, и ожидания растут. Visual Basic давно заслужил репутацию языка, существенно упрощающего процесс разработки пользовательского интерфейса, однако если подойти к нему с более современной меркой, то эта репутация может оказаться под угрозой.
В настоящее время уже недостаточно предоставляемых средой разработки средств для "перетаскивания" и размещения элементов управления пользовательского интерфейса и редактора меню с пустыми полями дл подстановки нужных значений. Помимо этого, хотелось бы, чтобы программисты могли повторно использовать свои старые разработки и применять стабильные интерфейсные конструкции как в рамках одного прикладного пакета, так и в разных программах. Язык Delphi 2.0 фирмы Borland, например, располагает хранилищем объектов, играющим роль библиотеки "стандартных" шаблонов прикладных программ, словарей данных, диалоговых окон, форм и других объектов. Для программирующих на Delphi при создании нового объекта исходным может быть любой объект из хранилища, при этом новый объект может воспроизводить или наследовать любые из его атрибутов, свойств и методов.
Вместе с тем в четвертой версии Visual Basic все еще не предусмотрено механизмов для повторного использования существующих программных фрагментов и форм. Хотя фирма Microsoft превозносит достоинства однородной организации диалоговых окон, меню, инструментальных панелей и других элементов в прикладном пакете Office, в Visual Basic по-прежнему предполагается, что проблемы проектировочных стандартов и их согласованности не существует как таковой. Всякий раз создание формы в Visual Basic вы начинаете с чистого листа, не извлекая никакой пользы из ранее созданных вами форм со скомпонованными элементами управления, процедурами обработки событий, меню и инструментальными панелями, которые могли бы прекрасно работать в новой форме.
Отсутствие возможности повторного использовани существующих конструкций особенно досаждает, когда речь заходит о новых стандартных элементах управлени Windows 95. Новые элементы пользовательского интерфейса, предусмотренные в Windows 95 (в их число входят элементы управления ListView и TreeView, напоминающие элементы модуля Explorer, предоставляют весьма полезные строительные блоки для разработки привлекательных и современных пользовательских интерфейсов. Однако их программные интерфейсы часто оказываются излишне сложными - Visual Basic никак не способствует разрешению этой проблемы.
Рассмотрим, например, имеющийся в Windows 95 элемент управления Toolbar (инструментальная панель). Общепринятый метод его применения состоит в следующем: вы добавляете к вашему проекту файл COMCTL32.OCX, а затем "перетаскиваете" с помощью мыши элементы управления Toolbar и ImageList (где хранятся растровые образы) в некую форму. После этого вы дополняете элемент управления ImageList через диалог Properties (Свойства) несколькими изображениями (в виде BMP- и ICO-файлов), а с помощью диалога свойств элемента управления Toolbar идентифицируете элемент ImageList как источник растровых образов инструментальной панели Toolbar и определяете расположение и внешний вид каждой кнопки инструментальной панели, включая выводимый на экран образ ImageList.
Этот процесс усложняется несколькими досадными ограничениями, свойственными элементам управлени ImageList и Toolbar, такими, как отсутствие средств дл элемента ImageList перестановки содержимого и дл предварительного просмотра содержимого элемента ImageList (что вынуждает вас выбирать изображени кнопок по численному индексу или текстовому признаку, но не по самому образу). Очевидно, что если бы разработчики COMCTL32.OCX приложили чуть больше усилий, они могли бы значительно облегчить жизнь программистам, работающим с Visual Basic 4.0, - особенно если учесть, что данную процедуру приходится повторять довольно часто. Справившись, несмотря на перечисленные трудности, с задачей организации инструментальной панели, вы вряд ли испытаете радость, выяснив, что не существует никакого способа сохранить ее для повторного использования в будущем.
Невозможность сохранить диалоговые окна и формы - досадный недостаток, но инструментальные панели, не подлежащие повторному применению, - явная нелепость, ведь они по самой своей сути предназначены для этого. На секунду забудьте о том, как скучно создавать инструментальные панели; подумайте о печальной участи конечного пользователя. Инструментальная панель становится по-настоящему эффективным средством пользовательского интерфейса только тогда, когда пользователь знаком с компоновкой панели и находит ее внешний вид и способ функционирования удобным для себя. Стандартизация инструментальных панелей в рамках программных пакетов, таких, как Microsoft Office и Lotus SmartSuite, - важный шаг на пути к обеспечению удобства их эксплуатации; различия между инструментальными панелями конкурирующих пакетов представляют собой серьезный барьер, препятствующий переходу от одного пакета к другому.
Поэтому мне кажется непостижимым, почему в Visual Basic нет средств загрузки и сохранения шаблонов инструментальных панелей, т. е. вы вынуждены создавать каждую инструментальную панель с нуля: размещать кнопки на панели, выбирать изображения для элемента ImageList и писать программу-обработчик для всех событий инструментальной панели. У вас должна быть возможность сохранения полных инструментальных панелей (в том числе элементов управления Toolbar и ImageList и программных модулей обработчика событий) и загрузки их в любой момент в другие формы. (Справедливости ради упомяну, что пока не видел инструментов разработки, которые бы располагали такой возможностью, но отсюда не следует, что необходимость в них отсутствует.)
Положение спасает то, что в отличие от предыдущих версий Visual Basic 4.0 располагает механизмом, позволяющим преодолевать некоторые недостатки его среды проектирования с помощью интерфейса модулей расширения. Интерфейс прикладного программирования (API) модулей расширения позволяет программистам Visual Basic создавать утилиты, взаимодействующие со средой разработки Visual Basic 4.0. Он дает возможность добавлять пункты в меню Add-Ins, манипулировать формами и элементами управления в активном проекте и реагировать на файловые события такие, как загрузка и сохранение файлов форм.
Интерфейс модулей расширения выглядит идеальным средством для решения проблем, касающихся реализации инструментальных панелей в Visual Basic, и, действительно, в конечном итоге мне удалось использовать его для построения модуля расширени Toolbar Wizard (Мастер инструментальной панели). Но процесс этот оказался нелегким. Я столкнулся со множеством функциональных пробелов в интерфейсе расширения и другими скрытыми ловушками.
Нельзя сказать, что я остался недоволен достигнутым результатом. Моя утилита Toolbar Wizard успешно решает самую сложную проблему, связанную с инструментальными панелями Visual Basic 4.0, позволяя сохранить инструментальную панель на диске и впоследствии вновь загрузить ее вместе с сопутствующим элементом ImageList и полным текстом обработчика событий в другую форму. Но меня удивило, насколько трудно было добиться нужного результата и как много на первый взгляд очевидных нужд не могут быть удовлетворены с помощью интерфейса расширения.
Большинство этих трудностей можно отнести на счет скудости документации интерфейса расширения. В печатной документации по Visual Basic 4.0 API расширения вообще не обсуждаются, а оперативная документация, приведенна в Help-файле (и дословно продублированная в руководстве Visual Basic Books Online), весьма бедна. В Help-файле приведены справочные описания различных объектов и методов, вкупе составляющих интерфейс расширения, но отсутствуют образцы программ, иллюстрирующие эти описания, а также настоящая обзорная информация о том, как работать с API расширения. На диске с Visual Basic имеются три образцовых проекта, реализованные с использованием средств расширения, но они не документированы, если не считать немногочисленных комментариев в тексте программ. Более того, три этих проекта дают далеко не всеобъемлющую картину использования API расширения; фактически, некоторые ключевые группы функций (такие, как методы, необходимые для добавления к проекту библиотек Reference и Typelibs) не применяются ни в одном из имеющихс проектов.
Однако еще больший недостаток, нежели пробелы в документации, - прорехи в API, под этим я подразумеваю почти бесконечный список задач, которые не решаются с помощью интерфейса расширения. Некоторые, но далеко не все из них описаны в публикации Microsoft Knowledge Base (#Q141931), озаглавленной "Limitations of the Visual Basic 4.0 Add-In Object Model" ("Ограничени объектной модели расширения Visual Basic 4.0"). Стать начинается с предупреждения о том, что "лишь часть среды разработки открыта для доступа через интерфейс расширения, и поэтому некоторые нужные вам функции могут отсутствовать". Замените слово "могут" на "почти наверняка будут", и вы получите довольно точное представление о том, что ожидает разработчика, который попытается работать с интерфейсом расширения.
Существуют и другие ограничения: модуль расширени может работать только с активной формой; он не может активизировать еще одну форму или модуль; не может устанавливать свойства объектов Datasource и Picture; не может получить доступ к инструментальной панели, меню, окнам Code (Программа), Debug (Отладка), Menubar (Линейка меню), Project (Проект), Properties (Свойства) и Toolbox (Комплект инструментов) любого элемента управления среды разработки Visual Basic. Он также неспособен определить, в каком режиме (выполнения, прерывания или отладки) находится в данный момент среда разработки. Перечисленные недочеты - лишь самые важные, сразу бросающиеся в глаза. С прочими "ограничениями" сталкиваешься непрерывно в процессе работы с API расширения.
Чем больше я узнавал об этих ограничениях, тем ниже становились требования к моему будущему модулю расширения Toolbar Wizard. Вначале я намеревалс создать исчерпывающий инструментальный набор дл конструирования инструментальных панелей - с тем чтобы вы получили возможность выполнения всех операций, необходимых для построения инструментальной панели, в том числе могли бы составить список ImageList и разместить кнопки через единую последовательность связанных диалоговых окон, а также загрузить и сохранить готовые шаблоны инструментальных панелей. Однако, учитывая препятствия, заложенные в API расширения, я счел разумным сосредоточиться на центральной задаче - обеспечить способ повторного использования инструментальных панелей.
Упрощенная утилита начинает свою работу с добавлени нового пункта с заголовком Toolbar Wizard в меню Add-Ins. Под ним появляются два размещаемых каскадом подпункта: Save current toolbar... (Сохранить текущую инструментальную панель) и Load toolbar... (Загрузить инструментальную панель).
Когда вы выбираете один из подпунктов, мастер Toolbar Wizard определяет, были ли внесены изменения в активную форму за время, прошедшее с момента ее последнего сохранения, и если да - предлагает вам сохранить их прежде, чем продолжить работу. Утилита Toolbar Wizard решает многие стоящие перед ней задачи, читая с диска или записывая на него текущий файл формы, поэтому так важно перед началом работы мастера Toolbar Wizard обновить содержимое этого файла.
Если вы указали, что хотите сохранить инструментальную панель, имеющуюся в текущем проекте, Toolbar Wizard выполнит промотр активной формы, с тем чтобы убедиться в наличии там хотя бы одного элемента управления Toolbar и одного элемента управлени ImageList. Затем "мастер" выводит на экран диалоговое окно Save Toolbar (Сохранить инструментальную панель), которое содержит подокно с ниспадающим списком, в который Toolbar Wizard заносит имена всех элементов управления инструментальной панели в активной форме, позволяя выбрать панель, подлежащую сохранению.
Щелкните на кнопке Save (Сохранить) диалогового окна, и утилита Toolbar Wizard выведет на экран стандартный диалог Save As (Сохранить как), в котором вы можете указать имя нового файла (TBR) инструментальной панели. Затем все свойства выбранной инструментальной панели и сопряженного с ней элемента управления ImageList заносятся в указанный файл вместе с текстом программы обработчика событий для всех девяти возможных событий инструментальной панели (ButtonClick, Change, Click, DblClick, DragDrop, DragOver, MouseDown, MouseMove, MouseUp).
При загрузке инструментальной панели описанный процесс протекает в обратном порядке. В диалоговом окне Load Toolbar (Загрузить инструментальную панель) вам представлены два подокна редактирования текста, в которых вы можете указать имена элементов управлени Toolbar и ImageList, добавляемых к вашей форме (по умолчанию используются имена tbwizTB1 и tbwizIL1). Когда вы щелкнете мышью на кнопке Open (Открыть), Toolbar Wizard выведет на экран стандартное диалоговое окно Open для выбора нужного TBR-файла. Затем "мастер" дополняет активную форму текущего проекта (использу указанные вами имена) определенными в файле элементами управления Toolbar и ImageList вместе со всеми сохраненными программами обработчика событий инструментальной панели.
Таково краткое описание утилиты Toolbar Wizard. Не слишком сложно, не правда ли? Но для того, чтобы сделать ее столь простой, потребовалось приложить немалые усилия.
Составление любой прикладной программы расширени Visual Basic 4.0 осуществляется в два этапа. Сначала организуются структуры, которые понадобятся модулю расширения для выполнения таких задач, как регистраци себя в диспетчере средств расширения (Add-In Manager) среды Visual Basic, добавление специальных пунктов в меню Add-In и идентификация методов, которые будут вызваны в ответ на события меню или файловые события. Эта стадия мало изменяется от проекта к проекту (в сущности генерирование шаблонного текста модул расширения для решения подобных задач было бы подходящей работой для модуля расширения).
Для выполнения этих служебных и интерфейсных функций наряду со стандартным программным модулем (MAIN.BAS) утилита Toolbar Wizard использует два классовых модул (CONNECT.CLS и TBMENUITEM.CLS) функций. Объект ConnectClass, определенный в первом классовом модуле, располагает двумя общедоступными методами: ConnectAddIn и DisconnectAddIn, которые вызываются диспетчером средств расширения Visual Basic, когда модуль расширения добавляется или удаляется из среды разработки Visual Basic. Процедура AfterClick класса tbMenuItem содержит программный фрагмент, выполняющийс тогда, когда пользователь выбирает один из пунктов меню Toolbar Wizard. Диспетчер средств расширения Add-In Manager автоматически отыскивает методы ConnectAddIn и DisconnectAddIn в модуле расширения, но, как показано ниже, вам необходимо знать, какой метод будет вызван в ответ на щелчок мыши в меню.
Интерфейс объектной модели VBIDE предоставляет доступ к обширной иерархии объектов, принадлежащих активному проекту в текущем экземпляре Visual Basic. Поскольку вы можете одновременно запускать более одного экземпляра Visual Basic 4.0 (на самом деле эту возможность надо использовать при тестировании программ расширения), наивысшую ступень иерархии занимает объект Application, который задает экземпляр Visual Basic 4.0, запустивший модуль расширения. У объекта Application три прямых потомка: объектная иерархия AddinMenu, с помощью которой модуль расширения определяет специальные элементы меню; объектная иерархи ActiveProject, через которую модуль расширения получает информацию о формах и компонентах; и объект FileControl, который может понадобиться модулю расширения для отслеживания самых разнообразных файловых событий в активном проекте, в том числе DoGetNewFileName, RequestChangeFileName и RequestWriteFile.
Во время загрузки утилиты Toolbar Wizard раздел описания ConnectClass создает экземпляры двух объектов VBIDE.MenuLine - tbWizLoadItem и tbWizSaveItem - дл хранения двух специальных элементов меню. Он также создает пару объектов tbMenuItem - tbWizLoadItemHandler и tbWizSaveItemHandler - для обработки щелчков мышью в меню. Затем метод ConnectAddIn использует эти объекты VBIDE.MenuLine для введения специальных элементов в меню Add-In Visual Basic:
Set tbWizMenu = VBInstance.AddInMenu.MenuItems.AddMenu("&Toolbar Wizard")
With tbWizMenu.MenuItems
Set tbWizLoadItem = .Add("&Save current toolbar...")
Set tbWizSaveItem = .Add("&Load toolbar...")
End With
Затем метод ConnectAddIn идентифицирует обработчик событий для каждой новой строки меню, передавая два объекта tbMenuItem методам двух объектов VBIDE.MenuLine:
tbWizLoadItemConnectID = tbWizLoadItem.ConnectEvents(tbWizLoadItemHandler)
tbWizSaveItemConnectID = tbWizSaveItem.ConnectEvents(tbWizSaveItemHandler)
Наконец, метод ConnectAddIn устанавливает параметры, называемых Operation и VBInstance, для двух объектов tbMenuItem. Параметр Operation представляет собой целочисленный флаг, неодходимый объекту tbMenuItem дл того, чтобы определить, какую операцию (загрузку или сохранение) следует инициировать, тогда как параметр VBInstance - объектная переменная, хранящая ссылку на объект Application, которая нужна объектам tbMenuItem, чтобы обращаться к любому из своих дочерних объектов:
tbWizLoadItemHandler.Operation = opSave
tbWizSaveItemHandler.Operation = opLoad
Set tbWizLoadItemHandler.VBInstance = VBInstance
Set tbWizSaveItemHandler.VBInstance = VBInstance
Когда пользователь выбирает один из пунктов меню, Visual Basic вызывает метод AfterClick для объекта tbMenuItem, связанного с пунктом меню. После первой проверки, выполняемой с тем, чтобы убедиться, что активная форма не имеет несохраненных изменений, AfterClick загружает либо форму Load Toolbar, либо форму Save Toolbar, в зависимости от значения параметра Operation объекта tbMenuItem:
If Not CheckForDirt() Then
Select Case Operation
Case opLoad
Load tbLoadForm
Case opSave
Load tbSaveForm
End Select
End If
End Sub
Следует отметить интересную особенность использования методом AfterClick команды Load (Загрузить) для инициализации отображения диалоговых окон Load Toolbar или Save Toolbar. Сначала намеревался организовать вывод диалоговых окон на экран в модальном режиме с помощью команды, подобной tbLoadForm-.Show 1. Однако по какой-то причине модально отображаемые диалоговые окна Add-In появляются в конце z-последовательности окон, позади любых открытых окон среды разработки или форм Visual Basic. Этим недостатком грешат большинство попадавшихся мне условно-бесплатных и коммерческих модулей расширения. Однако, немного поэкспериментировав, я понял, что применение команды Load в методе AfterClick поможет перенести окно диалога модуля расширения в начало z-последовательности. Я добился этого, включив в процедуру Form_Load каждого диалога следующие команды, уведомляющие диалог о том, что он должен показать и настроить на ввод свое собственное окно:
Show
Me.SetFocus
Метод DisconnectAddIn класса ConnectClass,
вызываемый в момент, когда диспетчер Add-In Manager
выгружает модуль расширения, выполняет действия,
противоположные тем, что предпринимались методом
ConnectAddIn в процессе настройки меню. Он использует
идентификаторы, полученные в результате сделанных
методом ConnectAddIn вызовов метода ConnectEvents, дл
разрыва связи между объектами tbMenuItem и специальными
элементами меню, затем передает в интерфейс VBIDE
указание удалить эти элементы меню:
tbWizLoadItem.DisconnectEvents tbWizLoadItemConnectID
tbWizSaveItem.DisconnectEvents tbWizSaveItemConnectID
VBInstance.AddInMenu.MenuItems.Remove tbWizMenu
Завершающая часть довольно-таки стандартного программного фрагмента модуля расширения - это процедура Sub Main() в файле MAIN.BAS. Она добавляет в модуль расширения ссылку на раздел "Add-Ins16" или "Add-Ins32" файла VB.INI - в случае если там нет таковой. Эти разделы INI-файла содержат перечень модулей расширения, допущенных к использованию диспетчером Add-In Manager. Активные модули расширени имеют значение 1, т. е. они подлежат автоматической загрузке вместе с Visual Basic:
[Add-Ins32]
Align.Connector=1
RegUtils.ConnectClass=0
DataFormDesigner.DFDClass=1
tbWiz.ConnectClass=1
На данном этапе модуль расширения Toolbar Wizard "знает", как поставить Visual Basic в известность о своем существовании и как реагировать на щелчки мышью на пунктах меню - выполнять эти функции должен практически любой модуль расширения. Теперь ему предстоит сделать нечто необычное.
Все специальные функции утилиты Toolbar Wizard представлены четырьмя подпрограммами: методом CheckforDirt класса tbMenuItem, методом Form_Load и SaveTB класса tbSaveForm и методом LoadTB класса tbLoadForm.
Метод CheckforDirt передает в вызывающую программу двоичное значение, служащее признаком того, есть ли в активной форме не сохраненные на диске изменения. Дл того чтобы выяснить это, метод перебирает весь набор компонентов Component объекта ActiveProject до тех пор, пока не найдет тот компонент, чье имя в точности совпадает с именем, полученным через параметр Name объекта ActiveProject.ActiveForm. Затем он исследует свойство IsDirty компонента Component и, воспользовавшись окном сообщений, предупреждает пользователя о необходимости сохранить форму (лист. 1).
Если выяснилось, что текущая форма должна быть сохранена, на экран выводится форма SavePrmpt с указанием пользователю выполнить это. Обычно дл подобной информации используется стандартное окно сообщений, но окна сообщений, созданные модулем расширения, как и модальные формы, часто оказываютс позади окна активного проекта. Чтобы обойти это препятствие, я создал новую форму, которую назвал SavePrmpt. Окно SavePrmpt выглядит совсем как стандартное окно сообщений, но поскольку в действительности оно представляет собой VB-форму, то может с помощью описанного выше метода переместиться на вершину z-последовательности.
В тексте подпрограммы CheckforDirt нет ничего особо сложного, но в нее включены обходные пути, которые пригодятся вам при работе с объектной иерархией VBIDE. Функцию можно существенно сократить, исключив перебор набора компонентов, в результате она будет выглядеть примерно так:
Лист. 1. Функция CheckForDirt передает в вызывающую программу двоичное значение, служащее признаком того, имеет ли активная форма несохраненные изменения.Private Function CheckForDirt() As Boolean Dim CurrFormName$, 1% With gobjIDEAppInst.ActiveProject CurrFormName$ = .ActiveForm.Properties.Item("Name") With .Components For i% = 0 To .Count - 1 If .Item(i%).Name = CurrFormName$ Then If .Item(i%).IsDirty Then CheckForDirt = True Beep Load SavePrmpt Exit Function End If End If Next End With End With End Function
Dim F As Form
Set F = gobjIDEAppInst.ActiveProject.ActiveForm
If F.IsDirty Then...
или даже так:
If gobjIDEAppInst.ActiveProject.ActiveForm.IsDirty Then...
Однако в отличие от объекта ProjectTemplate или Components у объекта Form параметр IsDirty не входит в его набор Properties. Организация цикла обработки компонентов Components объекта Project - простое решение, но для того, чтобы добиться его, вы должны хорошо разбираться во всем, что касается объектной иерархии, что трудно осуществимо с помощью скудной документации интерфейса модулей расширения или вялой утилиты просмотра объектов Visual Basic 4.0. Я чувствовал себя потерянным в море, пока не начал использовать для анализа и навигации в объектной иерархии превосходный браузер объектов VBA Companion фирмы Apex.
Метод Form_Load класса tbSaveForm (диалоговое окно Save Toolbar) ответствен за внесение имен всех элементов управления Toolbar и ImageList, обнаруженных им в активной форме, в пару полей со списками. Дл этого он в цикле проверяет элементы набора ControlTemplates активной формы с целью получить классовые имена каждого элемента управления в форме и затем сравнивает их с именами элементов управлени Toolbar и ImageList:
Dim i%, testControl As Object
With gobjIDEAppInst.ActiveProject.ActiveForm
For i% = 0 To .ControlTemplates.Count- 1
Set testControl = .ControlTemplates(i)
Select Case testControl
Case Is = "ComctlLib.ImageList"
Combol(1).AddItem testControl.Properties.Item("Name")
Case Is = "ComctlLib.Toolbar"
Combol(0).AddItem testControl.Properties.Item("Name")
Case Else
End Select
Next i%
End With
После того как вы идентифицировали инструментальную панель, которую вы желаете сохранить, и щелкнули на кнопке Save, метод SaveTB творит магическое действо - именно магическое. Долгое время я сомневался, возможно ли вообще сохранить инструментальные панели из интерфейса расширения. Я пробовал все новые способы дл извлечения информации, которая понадобилась бы утилите Toolbar Wizard для выполнения этой задачи через интерфейс расширения. Проделав три четверти работы, всякий раз убеждался, что API расширения неспособен предоставить эту информацию.
После многочисленных попыток и длительных экспериментов я понял, что в результате отсутстви доступа к данному и другим параметрами невозможно применение API расширения для извлечения информации, необходимой для воспроизведения инструментальной панели. Toolbar Wizard не может ни прочитать нужную информацию непосредственно через API, ни взаимодействовать с диалоговыми окнами Properties, предоставляющими эту информацию. Вместо этого утилите пришлось добывать информацию из файлов FRM и FRX активной формы. (В FRM-файлы входит описание содержимого формы в текстовом ASCII-формате, а также параметры и методы каждого из ее компонентов, тогда как FRX-файлы содержат двоичные данные, такие, как пиктограмма формы или растровые образы из ImageList.)
Таким образом, метод SaveTB начинает свою работу с того, что получает имена FRM- и FRX-файлов текущей формы, которые находятся в наборе FileNames объекта Component, представляющего данную форму. Программа идентифицирует объект формы внутри набора Components, сравнивая строку, переданную свойством Name объекта ActiveForm свойству Name каждого объекта Component:
With gobjIDEAppInst.ActiveProject
CurrFormName$ = .ActiveForm.Properties.Item("Name")
With .Components
For i% = 0 To .Count - 1
If .Item(i%).Name = CurrFormName$ Then
CurrFormFiles$(0) = .Item(i%).FileNames(0)
CurrFormFiles$(1) = .Item(i%).FileNames(1)
End If
Next
End With
End With
Использовав стандартный диалог Save As (Сохранить как) для того, чтобы получить имя для нового файла TBR, метод SaveTB должен определить имя элемента управлени ImageList, сопряженного с текущей инструментальной панелью. Для этого он открывает файл формы для ввода и читает его до тех пор, пока не обнаружит фрагмент кода, описывающий инструментальную панель. Затем, продолжив чтение файла, он отыскивает в найденном фрагменте определение свойств ImageList. В завершение после обнаружения и синтаксического разбора имени ImageList он закрывает файл ввода (лист. 2).
Лист. 2. Метод SaveTB открывает файл TBR для вывода и FRM-файл формы для ввода, затем читает каждую строку FRM-файла, отыскивая программные фрагменты, которые Visual Basic использует для описани указанных элементов управления Toolbar и Imagelist. Затем SaveTB копирует строки, содержащие определение инструментальной панели, и все строки между ними в файл TBR.Const imagelistID = "ImageList" tbTest$ = "Toolbar" & Combo1(0) ' __сначала получим имя imagelist Open Source$ For Input As #2 Do While Not EOF(2) Line Input #2, linetest$ If InStr(linetest$, tbTest$) > 0 Then '__мы обнаружили инструментальную панель Do Line Input #2. linetest$ linetest$ = Trim$(linetest$) If Left$(linetest$, 9) = imagelistID Then ' __мы обнаружили imagelist ii% = InStr(linetest$, Chr$(34)) If ii% > 0 Then linetest$ = Mid$(linetest$, ii% + 1) ii% = lnStr(linetest$, Chr$(34)) icTest$ = "ImageList " & Left$(linetest$, ii% - 1) End If Exit Do End If Loop If Len(icTest$) Then Exit Do End If Loop Close #2
Теперь, когда известно имя ImageList, программа SaveTB наконец располагает всей информацией, необходимой для сохранения инструментальной панели. С этой целью она открывает TBR-файл для вывода, еще раз открывает FRM-файл формы для ввода и вновь начинает читать строку за строкой содержимое FRM-файла, отыскивая программный фрагмент, используемый Visual Basic для описания заданных элементов управлени Toolbar и ImageList. Определение элемента управления с именем Toolbar1 начинается со строки с текстом "Begin ComctILib.Toolbar Toolbar1" и завершается строкой "End"; метод SaveTB копирует эти и все расположенные между ними строки в файл TBR.
Скопировав два определения элементов управления, метод SaveTB закрывает файл TBR, затем открывает его еще раз (тем самым переместив указатель на начало файла) и повторно читает для того, чтобы найти методы обработки событий элемента управления инструментальной панели, которые добавляет в конец TBR-файла (лист. 3).
Лист. 3. Функция SaveTB закрывает, затем открывает файл FRM, вновь читает его и вносит методы обработчика событий элемента управления Toolbar в конец TBR-файла.Close #2 tbEvent$ = Combo1(0) & "_" Terminator$ = "End Sub" Print #1, "TB_EVENTS" Open Soures$ For Input As #2 Do While Not EOF(2) Line Input #2, linetest$ If InStr(linetest$, tbEvent$) > 0 Then 'toolbar event Print #1, llnetest$ Do Line Input #2, linetest$ Print #1, linetest$ Loop Until Trim$(linetest$) = Terminator$ End If Loop Close #2 Close #1
И наконец, SaveTB копирует FRX-файл формы в каталог, в который помещен TBR-файл, присвоив скопированному файлу то же самое имя, что и у TBR-файла. Поэтому, если пользователь указал, что желает сохранить файл DBAPPS.TBR в каталоге C:\Toolbars, то FRX-файл будет скопирован в C:\Toolbars\DBAPPS.FRX. Хотя копирование всего файла FRX целиком может показаться излишне расточительным, обычно эти файлы довольно невелики, а копирование файла и легче, и надежнее, нежели попытка извлечь из него лишь необходимые двоичные данные.
Функция LoadTB диалогового окна Load Toolbar - именно благодаря ей, в конечном итоге, утилита Toolbar Wizard привносит частицу мира и покоя в истомленные души программистов, работающих с Visual Basic, дает возможность загрузить инструментальные панели, предварительно сохраненные функцией SaveTB, в любую форму Visual Basic.
Поскольку LoadTB может применяться для добавлени элементов управления Toolbar к формам проектов, не рассчитанных на использование стандартных элементов управления Windows 95, она начинает свою работу с того, что с помощью метода AddToolboxTypeLib объекта Project вносит эти элементы управления в инструментальную панель Visual Basic. Для этого она передает глобально уникальный идентификатор (GUID - globally unique identifier) библиотеки типов стандартных элементов управления, строки его полной и сокращенной версий и его имя файла методу AddToolboxTypeLib. (Если проект уже содержит ссылку на библиотеку типов COMCTL32, генерируется сообщение об ошибке, для чего и используется оператор On Error Resume Next.)
Dim guid$, major$, minor$, ocx$
guid$="{6B7E6392-850A-101B-AFC0-4210102A8DA7}"
major$="1.0"
minor$$="0"
ocx$="COMCTL32.OCX"
With gobjIDEAppInst.ActiveProject
On Error Resume Next
.AddToolboxTypelib guid$, major$, minor$, ocx$
On Error Goto 0
Следующим шагом метод LoadTB добавляет программный фрагмент из указанного пользователем TBR-файла к активной форме, для чего (1) использует метод RemoveComponent объекта ActiveProject с целью удалени указанной формы из активного проекта; (2) переименовывает файл формы, присваивая ему временное имя; (3) открывает временный файл и TBR-файл для ввода и новый целевой файл с исходным именем FRM-файла дл вывода; (4) копирует строку за строкой временного файла в целевой файл до тех пор, пока не достигнет начала раздела, описывающего элементы управления формы; (5) копирует описания элементов управления ImageList и Toolbar из TBR-файла в целевой файл; (6) копирует оставшуюся часть временного файла в целевой файл; и наконец, (7) копирует текст программы обработчика событий Toolbar из TBR-файла в целевой файл.
После завершения данной процедуры в целевом файле, который имеет то же имя, что и исходный FRM-файл, будет сохранено все содержимое исходного FRM-файла плюс описания элементов управления ImageList и Toolbar в самом начале раздела описания элементов управлени (который начинается после строки, указывающей свойство Width (ширина) формы) и обработчики событий дл элемента управления Toolbar в конце раздела, посвященного обработчику событий. (Места расположени этих описаний внутри каждой секции ничем не примечательны, за исключением того, что их легко обнаружить и этот подход, по всей видимости, отлично срабатывает.)
Исходный текст программы, предназначенной дл реализации этой функции, очень похож на зеркальное отображение функции SaveTB, поскольку LoadTB большей частью просто повторяет действия этой функции в обратном порядке. Однако, чтобы элементы управлени ImageList и Toolbar корректно работали в своем новом окружении, метод LoadTB должен внести два изменения в исходный текст, хранящийся в TBR-файле. Во-первых, он меняет все ссылки на элементы управления ImageList и Toolbar, чтобы отразить имена, указанные пользователем в диалоговом окне Load Toolbar (Загрузить инструментальную панель), с помощью простых приемов замены строк. Что более важно, ему необходимо заменить все ссылки на FRX-файл в описаниях элементов управления. Например, в определении для элемента управления ImageList, взятого из формы под названием Form2 и содержащего пять изображений, для указани местоположения растровых образов в FRX-файле будут использованы операторы, подобные перечисленным ниже:
i1 = "Form2.frx":11EC
i2 = "Form2.frx":13A3
i3 = "Form2.frx":155A
i4 = "Form2.frx":1799
i5 = "Form2.frx":1950
Прежде чем скопировать эти операторы в целевой файл,
LoadTB заменяет имя и путь к текущему FRX-файлу файла
TBR:
qm = Chr$(34) 'quotation mark
Const frx = ".frx"
frxTarg$ = ExtractFilePath$(gfnameTBFile) + ExtractFileRoot(gfnameTBFile)
...
If InStr(linetest$, frx) > 0 Then
i% = InStr(linetest$, qm)
linetest$ = Left$(linetest$, i%) + frxTarg$ + Mid$(linetest$,
(InStr(linetest$, frx)))
End If
Таким образом, определение ImageList в целевом файле
будет выглядеть примерно так:
i1 = "C:\Toolbars\DBAPPS.FRX":11EC
i2 = "C:\Toolbars\DBAPPS.FRX":13A3
i3 = "C:\Toolbars\DBAPPS.FRX":155A
i4 = "C:\Toolbars\DBAPPS.FRX":1799
i5 = "C:\Toolbars\DBAPPS.FRX":1950
Здесь мне удалось выполнить часть работы с помощью
средств автоматизации среды проектирования Visual
Basic. Visual Basic правильно загружает элемент
управления, содержащий ссылки на FRX-файл, не
принадлежащий текущей форме, но, как только форма
сохранена, образы элемента управления копируются в
собственный FRX-файл формы, и в ссылку вносятс
изменения, с тем чтобы она указывала на данный файл.
Таким образом, первоначальный FRX-файл TBR-файла
остается прежним и готовым для использования с другими
формами, в то время как собственный FRX-файл формы
содержит все необходимые ему для отображени
инструментальной панели растровые образы, скопированные
функцией LoadTB из TBR-файла.
LoadTB завершает работу, выдав объекту ActiveProject указание добавить форму, заново оснащенную инструментальной панелью, к проекту, используя метод AddFile объекта. При этом форма вносится в проект, а ее имя выделяется в окне Project, но форма не выводится на экран - таким образом, немедленное свидетельство того, что инструментальная панель была добавлена к проекту, отсутствует. Я решил, что этот недостаток - в сочетании с отсутствием команды вывода на экран указанной формы - недопустим, и прибег еще к одной хитрости. В заголовке окна Project выводиться корневая часть имени файла проекта, поэтому функция LoadTB передает этот корень методу AppActivate Visual Basic (тем самым активизиру окно Project), затем посылает код клавиши {ENTER} в окно Project, имитируя, таким образом, щелчок мыши на кнопке View Form для выделенного файла, и вы получаете именно ту форму, которую хотите просмотреть:
x$ = gobjIDEAppInst.ActiveProject.AddFile(Target$)
tmpfile$ = ExtractFileRoot$(gobjIDEAppInst.ActiveProject.FileName)
AppActivate tmpfile$
SendKeys "{ENTER}"
Этим заканчивается работа функции LoadTB и вместе с
ней перечень возможностей утилиты Toolbar Wizard. Все
приведенные в данной статье исходные тексты и программы
можно получить из оперативной информационной службы PC
Magazine Online.
В целом я доволен итогами работы над утилитой Toolbar Wizard, но она потребовала от меня гораздо больших усилий, чем следовало бы. API-расширения и иерархия объектов, которые предоставляет Visual Basic, слишком ограниченны. Можно только надеяться, что эти ограничения будут сняты в будущих версиях Visual Basic.
ОБ АВТОРЕ: Пол Боннер - независимый автор, системный консультант и специалист по визуальному программированию из Бостона. К нему можно обратиться по адресу pbonner@zd.com.