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

Элементы управления button и checkbox

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

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

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

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

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

Оно вызвано тем, что в XML-схеме задано значение атрибута щnLoad, а процедуры с соответствующим именем ещё нет. Поэтому следует прописать эту, а также остальные процедуры, указанные в XML-схеме.
Открываем редактор VBA и вставляем в него такой код:
Option Explicit

Sub RibbonLoading(ribbon As IRibbonUI)

End Sub

Sub getLabel(control As IRibbonControl, ByRef label)

End Sub

Sub getEnabled(control As IRibbonControl, ByRef enabled)

End Sub

Sub getPressed(control As IRibbonControl, ByRef pressed)

End Sub

Sub onAction(control As IRibbonControl, pressed As Boolean)

End Sub

Sub btnOnAction(control As IRibbonControl)

End Sub
Это "тела" процедур, имена которых были определены в XML-схеме. Наполним эти процедуры содержимым:
Option Explicit
'Глобальные переменые, необходимые для передачи состояния элементов управления _
между процедурами
Dim chb1_pressed As Boolean 'Состояние флажка
Dim chb1_enabled As Boolean 'Активность флажка
Dim btn1_enabled As Boolean 'Активность кнопки
Dim myRibbon As IRibbonUI 'Пользовательский интерфейс
Dim PressCounter As Integer 'Счётчик нажатий

Sub RibbonLoading(ribbon As IRibbonUI)
  '
  'Загрузка ленты
  '

  'Задаём состояние флажка и кнопки
  chb1_enabled = True
  btn1_enabled = chb1_pressed
  'Запоминаем наш интерфейс в переменную
  Set myRibbon = ribbon
End Sub

Sub getEnabled(control As IRibbonControl, ByRef returnedVal)
  '
  'Задаём активность элемента управления
  '

  'Определяем элемент управления, вызвавший процедуру
  Select Case control.ID
    Case "chb1" 'Устанавливаем активность флажка
      returnedVal = chb1_enabled
    Case "btn1" 'Устанавливаем активность кнопки
      returnedVal = btn1_enabled
  End Select
End Sub

Sub getPressed(control As IRibbonControl, ByRef returnedVal)
  '
  'Задаём состояние элемента управления
  '

  returnedVal = chb1_pressed
End Sub

Sub getLabel(control As IRibbonControl, ByRef returnedVal)
  '
  'Задаём текст элемента управления
  '

  'Определяем какой элемент управления вызвал эту процедуру
  Select Case control.ID
    Case "chb1" 'Задаём текст флажка
      returnedVal = "Нажат " & PressCounter & " раз. Текущее состояние: " & IIf(chb1_pressed, "установлен", "снят")
    Case "btn1" 'Задаём текст кнопки
      returnedVal = "Кнопка изменения состояния флажка"
  End Select
End Sub

Sub onAction(control As IRibbonControl, pressed As Boolean)
  '
  'Клик по флажку
  '

  'Запоминаем состояние флажка в глобальные переменные
  chb1_pressed = pressed
  btn1_enabled = pressed

  PressCounter = PressCounter + 1 'Инкремент счётчика

  'Обновляем элементы управления
  myRibbon.InvalidateControl control.ID 'флажок, вызвавший процедуру
  myRibbon.InvalidateControl "btn1" 'кнопка
End Sub

Sub btnOnAction(control As IRibbonControl)
  '
  'Нажатие на кнопку
  '

  chb1_pressed = Not chb1_pressed 'Изменяем значение переменной состояния флажка на противоположное
  PressCounter = PressCounter + 1 'Инкремент счётчика
  myRibbon.InvalidateControl "chb1" 'Обновляем флажок
End Sub

Пугаться не нужно, разберём всё по кирпичикам.
  1. Кирпичик №1. Объявление глобальных переменных
    'Глобальные переменые, необходимые для передачи состояния элементов управления _
    между процедурами
    Dim chb1_pressed As Boolean 'Состояние флажка Dim chb1_enabled As Boolean 'Активность флажка Dim btn1_enabled As Boolean 'Активность кнопки Dim myRibbon As IRibbonUI 'Пользовательский интерфейс Dim PressCounter As Integer 'Счётчик нажатий
    В самом начале кода объявляем глобальные переменные, используемые для взаимодействия с элементами управления на ленте и с самой лентой. С помощью этих переменных мы сможем объяснить флажку, какой текст ему нужно иметь, в какое состояние его поставил кнопка и т.д.

  2. Кирпичик №2. Загрузка пользовательского интерфейса. RibbonLoading
    Sub RibbonLoading(ribbon As IRibbonUI)
      '
      'Загрузка ленты
      '
    
      'Задаём состояние флажка и кнопки
      chb1_enabled = True
      btn1_enabled = chb1_pressed
      'Запоминаем наш интерфейс в переменную
      Set myRibbon = ribbon
    End Sub
    
    Эта процедура выполняется самой первой при загрузке пользовательского интерфейса. Как видно, она вызывается не пустой, а с параметром ribbon, содержащим ссылку на загружаемый интерфейс. В этой процедуре я устанавливаю значение переменных, определяющих активность кнопки и флажка, и запоминаю в свою переменную myRibbon ссылку на пользовательский интерфейс, чтобы обращаться к нему из других процедур.

  3. Кирпичик №3. Определение активности элемента управления. getEnabled
    Sub getEnabled(control As IRibbonControl, ByRef returnedVal)
      '
      'Задаём активность элемента управления
      '
      'Определяем элемент управления, вызвавший процедуру
      Select Case control.ID
        Case "chb1" 'Устанавливаем активность флажка
          returnedVal = chb1_enabled
        Case "btn1" 'Устанавливаем активность кнопки
          returnedVal = btn1_enabled
      End Select
    End Sub
    

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

  4. Кирпичик №4. Установка состояния флажка. getPressed"
    Sub getPressed(control As IRibbonControl, ByRef returnedVal)
      '
      'Задаём состояние элемента управления
      '
    
      returnedVal = chb1_pressed
    End Sub
    

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

  5. Кирпичик №5. Установка текста элемента управления. getLabel
    Sub getLabel(control As IRibbonControl, ByRef returnedVal)
      '
      'Задаём текст элемента управления
      '
    
      'Определяем какой элемент управления вызвал эту процедуру
      Select Case control.ID
        Case "chb1" 'Задаём текст флажка
          returnedVal = "Нажат " & PressCounter & " раз. Текущее состояние: " & IIf(chb1_pressed, "установлен", "снят")
        Case "btn1" 'Задаём текст кнопки
          returnedVal = "Кнопка изменения состояния флажка"
      End Select
    End Sub
    

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

  6. Кирпичик №6. Клик на флажке. onAction
    Sub onAction(control As IRibbonControl, pressed As Boolean)
      '
      'Клик по флажку
      '
    
      'Запоминаем состояние флажка в глобальные переменные
      chb1_pressed = pressed
      btn1_enabled = pressed
    
      PressCounter = PressCounter + 1 'Инкремент счётчика
    
      'Обновляем элементы управления
      myRibbon.InvalidateControl control.ID 'флажок, вызвавший процедуру
      myRibbon.InvalidateControl "btn1" 'кнопка
    End Sub
    

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

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

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

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

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

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

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