Создание интерфейса документа. Объект CommandBars
Коллекция CommandBarControls и ее элементы
В эту коллекцию входят элементы, располагаемые на одной инструментальной панели. Коллекция обладает только традиционными свойствами и методами. Единственное, что следует рассмотреть - это метод Add и его параметры:
Function Add([Type], [Id], [Parameter], [Before], [Temporary]) As CommandBarControl
Метод позволяет добавлять новые элементы в коллекцию. Его параметры:
- Type устанавливает тип элемента и класс этого элемента, возвращаемый функцией Add. Если значением Type является константа msoControlButton, то возвращается объект класса CommandBarButton, для значений: msoControlEdit, msoControlDropdown, msoControlComboBox - возвращается объект класса CommandBarComboBox и, наконец, если тип имеет значение msoControlPopup, то возвращаемый класс объекта - CommandBarPopup
- Id задает идентификатор встроенного элемента, добавляемого в коллекцию. Если он опущен, то на панели появится пользовательский, пустой элемент, свойства которого позднее следует определить.
- Parameter для встроенных элементов используется приложением при запуске команды, для пользовательских элементов может хранить некоторую информацию об элементе.
- Before задает местоположение элемента на панели, если он опущен, элемент добавляется в конец панели.
- Temporary является булевым параметром. Его значение True говорит о том, что элемент является временным и будет удален с панели при закрытии приложения. По умолчанию элементы являются постоянными.
Хотя все создаваемые элементы панели относятся к одному из трех различных классов, все они принадлежат также объединяющему классу CommandBarControl. Мы не будем подробно останавливаться на всех свойствах и методах, как отдельных классов, так и объединяющего элементы класса. Рассмотрим только, как решить главную для команд задачу - выполнить некоторую процедуру в ответ на выбор команды меню или щелчок кнопки. Действия, выполняемые командой, должны быть запрограммированы в виде макроса на языке VBA. Напомним, что макрос - это процедура без параметров. Однако есть некоторый способ передачи информации в исполняемый макрос. Для этого можно использовать параметр Parameter, задаваемый при создании элемента или, что удобнее, свойство Parameter. Конечно, для всех встроенных элементов макрос, задающий команду, уже написан. Но для собственных элементов его нужно написать самому, а затем макрос связать с элементом. Свойство OnAction, которым обладают все элементы панели, позволяет связать элемент с исполняемым макросом. Формально свойство является строкой, задающей имя макроса, который и будет вызываться в ответ на выбор пользователя.
Давайте перейдем теперь к рассмотрению задач, возникающих при создании интерфейса. Я начну c рассмотрения процедур, реализующих некоторые дополнительные свойства изучаемых нами объектов.
Exist-функции
К сожалению, у объектов CommandBars, CommandBar, CommandBarPopUp отсутствует весьма полезное свойство, проверяющее существование элемента в коллекции, на панели или меню. Оно необходимо при добавлении элемента, поскольку прежде чем добавить элемент, следует проверить, не существует ли он уже в коллекции. Эта проверка необходима еще и потому, что добавление выполняется вне зависимости от присутствия добавляемого элемента в коллекции (панели, меню) и тем самым порождает копии элементов, что, конечно же, нежелательно. Я написал три функции, реализующие свойство Exist у рассматриваемых объектов:
Public Function ExistCommandBar(PanelName As String) As Boolean 'Возвращает True, если в коллекции CommandBars 'существует панель с именем PanelName Dim bar As CommandBar, Exist As Boolean Exist = False For Each bar In CommandBars If bar.Name = PanelName Or bar.NameLocal = PanelName Then Exist = True Exit For End If Next bar ExistCommandBar = Exist End Function Public Function ExistControl(Panel As CommandBar, _ Capt As String) As Boolean 'Возвращает True, если в коллекции Controls ' панели с именем Panel существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In Panel.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistControl = Exist End Function Public Function ExistItem(CtrlPopUp As CommandBarPopup, _ Capt As String) As Boolean 'Возвращает True, если в меню CtrlPopUp ' существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In CtrlPopUp.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistItem = Exist End FunctionЛистинг 3.1.
Первая из них проверяет существование панели с заданным именем в коллекции панелей. Обратите внимание, имя можно задавать и по-русски, поскольку проверяются оба свойства Name и NameLocal. Вторая функция проверяет на заданной панели существование элемента с указанным заголовком. Третья функция выполняет аналогичную операцию для меню - объекта CommandBarPopUp.
Поиск элементов
Казалось бы, что с поиском элементов проблем нет, поскольку объекты CommandBars и Commandbar имеют метод FindControl, а в Office 2000 появился и новый метод FindControls. Эти методы, к сожалению, не всегда применимы, поскольку имеют весьма существенный недостаток. Ключом поиска является значение ID или Tag, но в момент поиска значения этих свойств, как правило, не известно. Чаще всего, известен лишь заголовок разыскиваемого элемента, не включенный в число параметров стандартных методов поиска. Поэтому я предпочел написать собственную процедуру поиска. Приведу эту процедуру и заодно расскажу об одной не очень приятной особенности коллекции панелей CommandBars:
Public Function MyFindControl(Capt As String) As CommandBarControl 'Поиск по заголовку 'Возвращает в качестве результата первый элемент с указанным заголовком Dim bar As CommandBar Dim Ctr As CommandBarControl Dim i As Integer For i = 1 To 9 Select Case i Case 1 Set bar = CommandBars("File") Case 2 Set bar = CommandBars("Edit") Case 3 Set bar = CommandBars("View") Case 4 Set bar = CommandBars("Insert") Case 5 Set bar = CommandBars("Format") Case 6 Set bar = CommandBars("Tools") Case 7 Set bar = CommandBars("Table") Case 8 Set bar = CommandBars("Window") Case 9 Set bar = CommandBars("Help") End Select For Each Ctr In bar.Controls If Ctr.Caption = Capt Then Set MyFindControl = Ctr Exit Function End If Next Ctr Next i For Each bar In CommandBars 'Debug.Print bar.name For Each Ctr In bar.Controls If Ctr.Caption = Capt Then Set MyFindControl = Ctr Exit Function End If Next Ctr Next bar End FunctionЛистинг 3.2.
Эта функция похожа на функцию FindControl коллекции CommandBars, также в случае успеха возвращается первый найденный элемент класса CommandBarControl. Разница состоит в том, что ключом поиска является заголовок искомого элемента. Но я хочу обратить внимание на реализацию данного метода. Заметьте, несмотря на то, что в основной части этой функции организован перебор по всей коллекции CommandBars, этому поиску предшествует поиск на отдельных элементах этой коллекции. Дело в том, что не все панели, реально присутствующие в коллекции, могут быть получены путем перебора в стандартном цикле For Each, индексы этих элементов выходят за диапазон, заданный значением Count этой коллекции. Таким образом, можно получить, например, доступ к панели "File"
Set bar = CommandBars("File")Листинг 3.3.
Но нельзя получить этот элемент в процессе перебора элементов коллекции CommandBars. Вместе с тем, многие команды меню находятся именно на этих панелях. Так, команда меню "Сохранить как " присутствует только на панели "File", в то же время другие команды, как, например, "Открыть", находятся одновременно и на панели "Standard". Справедливости ради, стоит заметить, что, если Вы знаете ID элемента, то для его поиска можно использовать стандартный метод поиска FindControl, который ведет поиск на всех панелях коллекции. Если же перебор панелей организуется самостоятельно, то следует считаться с "жучком", не позволяющим автоматически получить все элементы коллекции CommandBars.
Добавление и удаление панелей и других элементов
Начнем с процедуры, позволяющей в любом из приложений Office 2000 добавить в коллекцию CommandBars собственную инструментальную панель с заданным именем. Напомним, что хотя эта задача и решается одним оператором, поскольку у коллекции есть специальный метод Add, но корректная реализация требует предварительной проверки существования в коллекции панелей элемента с таким именем. Вот процедура, решающая эту задачу и использующая ранее написанную функцию Exist:
Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True End SubЛистинг 3.4.
Говоря о коллекции CommandBars и добавлении пользовательских панелей в эту коллекцию, следует иметь в виду одно обстоятельство. В разных приложениях Office 2000 есть свои нюансы использования этих коллекций. В приложении Word можно рассматривать коллекцию панелей, связанную с конкретным документом, и общую коллекцию, связанную с проектом Normal. Поэтому при работе в приложении Word, при рассмотрении коллекции панелей, связанной с документом, следует пользоваться уточненным именем ActiveDocument.CommandBars. В противном случае речь будет идти об общей коллекции, связанной с проектом Normal. При работе в Excel все пользовательские панели будут автоматически добавляться в коллекцию, связанную с документом и не будут появляться при открытии других документов Excel. В приложении Outlook программный проект, частью которого является коллекция, существует в единственном экземпляре, аналогично проекту Normal. Поэтому добавляемые панели будут распространяться на все документы Outlook.
Говоря о приложении Word и добавлении пользовательских панелей, хочу рассказать еще об одном небольшом "жучке" и как с ним бороться.
В приложении Word невозможно программно добавить панель, связанную с документом. Добавляемая программно панель будет всегда связана с проектом Normal и, следовательно, будет доступна для всех документов Word, что не всегда является желательным. Как уже отмечалось, свойство Context, которое должно обеспечивать связывание панели с документом, корректно не работает. Поэтому, если добавляемую панель необходимо связать только с конкретным документом, то работу по добавлению панели необходимо проделать вручную.
Рассмотрим еще одну процедуру добавления панели в коллекцию. Она является небольшой модификацией предыдущей процедуры, позволяя добавить панель головного меню. Одновременно, процедура отключает все остальные панели. Я буду использовать эту процедуру при создании собственного интерфейса документа в последующем примере.
Public Sub AddMainPanel(MainPanel As String) 'Выключаем все панели документа Dim bar As CommandBar For Each bar In CommandBars bar.Enabled = False Next bar 'Добавляем и включаем новую панель главного меню If Not ExistCommandBar(MainPanel) Then Call CommandBars.Add(name:=MainPanel, Position:=msoBarTop, _ MenuBar:=True, Temporary:=False) End If CommandBars(MainPanel).Enabled = True CommandBars(MainPanel).Visible = True End SubЛистинг 3.5.
Поскольку панели добавляются, то их нужно уметь и удалять, - операция Add должна иметь и обратную операцию Remove (Delete). Удаление возможно только для пользовательских панелей и невозможно, естественно, для встроенных панелей. Выполняется удаление достаточно просто, но и здесь желательны предварительные проверки:
Public Sub DelPanel(PanelName As String) 'Удаляет пользовательскую панель 'из коллекции CommandBars If ExistCommandBar(PanelName) _ And Not CommandBars(PanelName).BuiltIn Then CommandBars(PanelName).Delete End If End SubЛистинг 3.6.
Приведем еще одну обратную операцию. Поскольку в процедуре AddMainPanel все панели документа были отключены (это очень опасная операция), то необходимо иметь процедуру, восстанавливающую отключенные панели:
Public Sub ResetMainMenu() Dim CstmBar As CommandBar 'Включаем все панели For Each CstmBar In CommandBars CstmBar.Enabled = True Next CstmBar Set CstmBar = CommandBars.Item("Menu Bar") CstmBar.Visible = True End SubЛистинг 3.7.
Добавлять, конечно, нужно не только панели, но и пункты меню в уже созданную панель, команды в уже созданное меню и так далее. Несмотря на то, что существует метод Add для таких ситуаций, целесообразно написать собственное расширение, позволяющее перед добавлением производить проверку на существование в коллекции добавляемого элемента. Приведем примеры таких корректных расширений методов Add:
Public Sub AddBuiltinMenu(Panel As String, MenuName As String, _ Num As Variant, item As CommandBarPopup) 'Добавляет встроенный пункт меню с именем MenuName 'на панель Panel Dim bar As CommandBar, ider As Variant Dim Ctrl As CommandBarControl Set bar = CommandBars(Panel) If Not ExistControl(bar, MenuName) Then Set Ctrl = MyFindControl(MenuName) 'Если найден If Not (Ctrl Is Nothing) Then ider = Ctrl.ID bar.Controls.Add Type:=msoControlPopup, _ ID:=ider, Before:=Num Else: Exit Sub End If End If Set item = bar.Controls(MenuName) End SubЛистинг 3.8.
В процедуре AddBuiltinMenu встроенный пункт меню с именем MenuName вначале разыскивается в коллекции панелей, а затем добавляется на указанную панель Panel. Параметр Num указывает местоположение добавляемого элемента, а параметр item является результатом, возвращая добавленный пункт меню, как объект класса CommandBarPopup. В следующей процедуре в меню добавляется встроенная команда:
Public Sub AddItem(item As CommandBarPopup, name As String) Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then 'Найти встроенный элемент в коллекции встроенных панелей Set Ctrl = MyFindControl(name) 'Если найден If Not (Ctrl Is Nothing) Then item.Controls.Add Type:=msoControlButton, ID:=Ctrl.ID End If End If End SubЛистинг 3.9.
Приведем еще парочку процедур добавления:
Public Sub AddCustomItem(item As CommandBarPopup, _ name As String, Act As String) 'Добавляет команду меню 'Параметр Act задает имя макроса, реализующего команду Dim Ctrl As CommandBarControl If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlButton) Ctrl.Caption = name Ctrl.OnAction = Act End If End Sub Public Function AddCustomPopup(item As CommandBarPopup, _ name As String) As CommandBarPopup 'Добавляет подменю в меню, 'возвращая объект подменю в качестве результата Dim Ctrl As CommandBarPopup If Not ExistItem(item, name) Then Set Ctrl = item.Controls.Add(Type:=msoControlPopup) Ctrl.Caption = name End If Set AddCustomPopup = item.Controls(name) End FunctionЛистинг 3.10.