Разбираем по кусочкам новый «ленточный» интерфейс Word 2007. Здесь нет и не будет советов о том, как его сделать похожим на Word 2003. Только усовершенствования уже существующего. Также приглашаю посетить мой блог, посвященный работе с макросами в Word.

Напоминаю, что все коды программ, приведенных в блоге, вы используете на свой страх и риск. Не забывайте создавать резервные копии.

суббота, 5 июня 2010 г.

Динамические элементы управления button и checkbox

Как выстраивать стандартные элементы управления на ленте в желаемом порядке я уже рассказывал.
Как добавить на ленту собственные элементы управления и научить их откликаться на действия пользователя я буду рассказывать в серии заметок так, чтобы это не вызывало трудностей у новичков.
Чтобы пользовательскую ленту научить откликаться на действия пользователя, мало просто задать расположение элементов управления. Необходимо, используя язык VBA, прописать действия, выполняемые тем или иным элементом управления, а также внутри XML-кода указать, что работа того или иного элемента управления контролируется извне.
Начнём с двух, на мой взгляд самых простых элементов управления: кнопки (Button) и флажка(CheckBox).
Разберём простенький пример. Поместим на ленту новую вкладку с именем «Тест», в ней создадим группу с тем же именем. В группе разместим флажок (checkbox) и кнопку. При изменении состояния флажка будет изменяться активность кнопки, а также текст флажка, отображающий количество нажатий на нём и его состояние. Кнопкой просто будем устанавливать или снимать флажок.
XML-схема пользовательского интерфейса будет такой:
1 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"
3 onLoad="RibbonLoading">
4 <ribbon startFromScratch="false">
5 <tabs>
6 <tab id="Tab1" label="Тест">
7 <group id="Tab1_gr1" label="Тест">
8 <checkBox id="chb1"
9 getLabel="getLabel"
10 getEnabled="getEnabled"
11 getPressed="getPressed"
12 onAction="onAction" />
13 <button id="btn1"
14 getLabel="getLabel"
15 getEnabled="getEnabled"
16 onAction="btnOnAction" />
17 </group>
18 </tab>
19 </tabs>
20 </ribbon>
21 </customUI>

Процесс создания XML-кода с помощью RXE уже объяснялся, поэтому остановлюсь только на отличиях. XML-строки, соответствующие кнопке и флажку, добавляются кнопками и . Атрибуты можно писать вручную, а можно, и даже нужно, вводить, используя возможности RXE. В последней на сегодняшний день версии за ввод разнообразных атрибутов отвечают три выпадающих списка, расположенных над основным полем редактирования.
Код отличается от приведённых раньше прежде всего тем, что в нём я не задаю явно текст, отображаемый на элементе управления. Вместо этого прописан набор атрибутов, начинающихся со слова get. По порядку, что они означают:
  1. getLabel — атрибут, определяющий имя процедуры, которая задаёт текст элемента управления;

  2. getEnabled — атрибут, определяющий имя процедуры, устанавливающей активность элемента управления;

  3. getPressed — атрибут, определяющий имя процедуры, устанавливающей состояние элемента управления. Этот атрибут определён только для элементов управления, имеющих фиксированные состояния: checkbox и togglebutton.

Кроме этих атрибутов, имеется ещё один — onAction, который определяет имя процедуры, выполняющейся при клике мышью на элементе управления. Также у элемента пользовательского интерфейса, определяемого тегом customUI, я задал атрибут onLoad, который, как наверное уже понятно, определяет процедуру, выполняющуюся при загрузке пользовательского интерфейса.
Имена процедур, задаваемые атрибутам, могут быть совершенно произвольными, но, желательно, понятными разработчику :)
Схему создали. Сохраняем документ и открываем в Word. При этом получим сообщение:

Оно вызвано тем, что в XML-схеме задано значение атрибута щnLoad, а процедуры с соответствующим именем ещё нет. Поэтому следует прописать эту, а также остальные процедуры, указанные в XML-схеме.
Открываем редактор VBA и вставляем в него такой код:
1 Option Explicit
2
3 Sub RibbonLoading(ribbon As IRibbonUI)
4
5 End Sub
6
7 Sub getLabel(control As IRibbonControl, ByRef label)
8
9 End Sub
10
11 Sub getEnabled(control As IRibbonControl, ByRef enabled)
12
13 End Sub
14
15 Sub getPressed(control As IRibbonControl, ByRef pressed)
16
17 End Sub
18
19 Sub onAction(control As IRibbonControl, pressed As Boolean)
20
21 End Sub
22
23 Sub btnOnAction(control As IRibbonControl)
24
25 End Sub

Это "тела" процедур, имена которых были определены в XML-схеме. Наполним эти процедуры содержимым:
1 Option Explicit
2 'Глобальные переменые, необходимые для передачи состояния элементов управления _
3 между процедурами
4 Dim chb1_pressed As Boolean 'Состояние флажка
5 Dim chb1_enabled As Boolean 'Активность флажка
6 Dim btn1_enabled As Boolean 'Активность кнопки
7 Dim myRibbon As IRibbonUI 'Пользовательский интерфейс
8 Dim PressCounter As Integer 'Счётчик нажатий
9
10 Sub RibbonLoading(ribbon As IRibbonUI)
11 '
12 'Загрузка ленты
13 '
14
15 'Задаём состояние флажка и кнопки
16 chb1_enabled = True
17 btn1_enabled = chb1_pressed
18 'Запоминаем наш интерфейс в переменную
19 Set myRibbon = ribbon
20 End Sub
21
22 Sub getEnabled(control As IRibbonControl, ByRef returnedVal)
23 '
24 'Задаём активность элемента управления
25 '
26
27 'Определяем элемент управления, вызвавший процедуру
28 Select Case control.ID
29 Case "chb1" 'Устанавливаем активность флажка
30 returnedVal = chb1_enabled
31 Case "btn1" 'Устанавливаем активность кнопки
32 returnedVal = btn1_enabled
33 End Select
34 End Sub
35
36 Sub getPressed(control As IRibbonControl, ByRef returnedVal)
37 '
38 'Задаём состояние элемента управления
39 '
40
41 returnedVal = chb1_pressed
42 End Sub
43
44 Sub getLabel(control As IRibbonControl, ByRef returnedVal)
45 '
46 'Задаём текст элемента управления
47 '
48
49 'Определяем какой элемент управления вызвал эту процедуру
50 Select Case control.ID
51 Case "chb1" 'Задаём текст флажка
52 returnedVal = "Нажат " & PressCounter & " раз. Текущее состояние: " & IIf(chb1_pressed, "установлен", "снят")
53 Case "btn1" 'Задаём текст кнопки
54 returnedVal = "Кнопка изменения состояния флажка"
55 End Select
56 End Sub
57
58 Sub onAction(control As IRibbonControl, pressed As Boolean)
59 '
60 'Клик по флажку
61 '
62
63 'Запоминаем состояние флажка в глобальные переменные
64 chb1_pressed = pressed
65 btn1_enabled = pressed
66
67 PressCounter = PressCounter + 1 'Инкремент счётчика
68
69 'Обновляем элементы управления
70 myRibbon.InvalidateControl control.ID 'флажок, вызвавший процедуру
71 myRibbon.InvalidateControl "btn1" 'кнопка
72 End Sub
73
74 Sub btnOnAction(control As IRibbonControl)
75 '
76 'Нажатие на кнопку
77 '
78
79 chb1_pressed = Not chb1_pressed 'Изменяем значение переменной состояния флажка на противоположное
80 PressCounter = PressCounter + 1 'Инкремент счётчика
81 myRibbon.InvalidateControl "chb1" 'Обновляем флажок
82 End Sub

Пугаться не нужно, разберём всё по кирпичикам.

  1. 2 'Глобальные переменые, необходимые для передачи состояния элементов управления _
    3 между процедурами
    4 Dim chb1_pressed As Boolean 'Состояние флажка
    5 Dim chb1_enabled As Boolean 'Активность флажка
    6 Dim btn1_enabled As Boolean 'Активность кнопки
    7 Dim myRibbon As IRibbonUI 'Пользовательский интерфейс
    8 Dim PressCounter As Integer 'Счётчик нажатий
    9

    В самом начале кода объявляем глобальные переменные, используемые для взаимодействия с элементами управления на ленте и с самой лентой. С помощью этих переменных мы сможем объяснить флажку, какой текст ему нужно иметь, в какое состояние его поставил кнопка и т.д.


  2. 10 Sub RibbonLoading(ribbon As IRibbonUI)
    11 '
    12 'Загрузка ленты
    13 '
    14
    15 'Задаём состояние флажка и кнопки
    16 chb1_enabled = True
    17 btn1_enabled = chb1_pressed
    18 'Запоминаем наш интерфейс в переменную
    19 Set myRibbon = ribbon
    20 End Sub
    21

    Эта процедура выполняется самой первой при загрузке пользовательского интерфейса. Как видно, она вызывается не пустой, а с параметром ribbon, содержащим ссылку на загружаемый интерфейс. В этой процедуре я устанавливаю значение переменных, определяющих активность кнопки и флажка, и запоминаю в свою переменную myRibbon ссылку на пользовательский интерфейс, чтобы обращаться к нему из других процедур.


  3. 22 Sub getEnabled(control As IRibbonControl, ByRef returnedVal)
    23 '
    24 'Задаём активность элемента управления
    25 '
    26
    27 'Определяем элемент управления, вызвавший процедуру
    28 Select Case control.ID
    29 Case "chb1" 'Устанавливаем активность флажка
    30 returnedVal = chb1_enabled
    31 Case "btn1" 'Устанавливаем активность кнопки
    32 returnedVal = btn1_enabled
    33 End Select
    34 End Sub
    35

    После того, как лента загружена, начинается прорисовка элементов управления. Насколько я заметил, элементы управления прорисовываются сверху вниз и справа налево. При этом вызываются процедуры, заданные в атрибутах, начинающихся с get.
    Эта процедура вызывается для каждого элемента управления, чтобы определить его активность. В качестве параметров процедуры передаётся сам элемент управления (control) и ссылка на свойство, которое у этого элемента нужно изменить. В данное процедуре таким свойством является Enabled, ссылка на которое передана в переменной returnedVal.
    В данном случае внутри процедуры оператором Select…Case определяется какой элемент управления её вызвал и в переменную, содержащую ссылку на свойство, записывается значение глобальной переменной, определённой выше. Поскольку в процедуре RibbonLoading я уже задал значения переменным chb1_enabled и btn1_enabled (true и false соответственно), то флажок будет отображаться активным, а кнопка — неактивной.


  4. 36 Sub getPressed(control As IRibbonControl, ByRef returnedVal)
    37 '
    38 'Задаём состояние элемента управления
    39 '
    40
    41 returnedVal = chb1_pressed
    42 End Sub
    43

    Это совсем просто, если вы «раскусили» предыдущий кирпичик. Состояние флажка определяется значением глобальной переменной chb1_pressed. Задать значение этой переменной можно где угодно. Посколько до этого мы его ещё нигде не задавали, то оно равно false и, соответственно, флажок будет снят. В эту процедуру передаются те же параметры, что и в предыдущую, только под returnedVal уже подразумевается свойство Checked. Эта процедура будет вызываться только для флажка.


  5. 44 Sub getLabel(control As IRibbonControl, ByRef returnedVal)
    45 '
    46 'Задаём текст элемента управления
    47 '
    48
    49 'Определяем какой элемент управления вызвал эту процедуру
    50 Select Case control.ID
    51 Case "chb1" 'Задаём текст флажка
    52 returnedVal = "Нажат " & PressCounter & " раз. Текущее состояние: " & IIf(chb1_pressed, "установлен", "снят")
    53 Case "btn1" 'Задаём текст кнопки
    54 returnedVal = "Кнопка изменения состояния флажка"
    55 End Select
    56 End Sub
    57

    Процедура, аналогичная getEnabled, только returnedVal определяет текст, отображаемый на элементе управления. В этой процедуре также используется глобальная переменная chb1_pressed, чтобы правильно отображать информацию о состоянии флажка.


  6. 58 Sub onAction(control As IRibbonControl, pressed As Boolean)
    59 '
    60 'Клик по флажку
    61 '
    62
    63 'Запоминаем состояние флажка в глобальные переменные
    64 chb1_pressed = pressed
    65 btn1_enabled = pressed
    66
    67 PressCounter = PressCounter + 1 'Инкремент счётчика
    68
    69 'Обновляем элементы управления
    70 myRibbon.InvalidateControl control.ID 'флажок, вызвавший процедуру
    71 myRibbon.InvalidateControl "btn1" 'кнопка
    72 End Sub
    73

    Эта процедура, вызывается, когда пользователь кликаем мышью по флажку. Первый параметр, который передаётся в этой процедуре, это всё тот же элемент управления, вызвавший её. Вторым параметром идёт состояние элемента управления после клика на нём. Именно с помощью этого второго параметра мы передаём в глобальные переменные информацию о состоянии элемента управления.
    Важной частью этой процедуры являются строки, содержащие вызов метода InvalidateControl. Вызов этого метода обновляет состояние указанного элемента управления. При этом обновлении повторно вызываются все процедуры, описанные в атрибутах, начинающихся с get. В данном случае, обновляется флажок (с изменённым текстом) и кнопка (с изменённой активностью)


  7. 74 Sub btnOnAction(control As IRibbonControl)
    75 '
    76 'Нажатие на кнопку
    77 '
    78
    79 chb1_pressed = Not chb1_pressed 'Изменяем значение переменной состояния флажка на противоположное
    80 PressCounter = PressCounter + 1 'Инкремент счётчика
    81 myRibbon.InvalidateControl "chb1" 'Обновляем флажок
    82 End Sub

    Эта процедура совершенно прозрачна и, думаю, не требует пояснений.

После введения кода. Документ нужно сохранить и повторно открыть. Если всё было сделано правильно, то выглядеть это будет так:
Сразу после открытия

Первый клик по флажку, чтобы активировать кнопку.

Нажатие на кнопку, чтобы изменить состояние флажка. Событие onAction для флажка в этом случае не вызывается! Поэтому кнопка остаётся активной.

Готовый пример файла