понедельник, 28 июня 2010 г.

Элемент dropDown — выпадающий список

Как раз раздумывал над очередной темой, как её подсказала сама жизнь: просьба рассказать об элементе comboBox и сетования одного из посетителей WordExpert на неудобную работу с автотекстом. Постараюсь убить двух зайцев сразу.
Итак, задача: сформировать список из элементов автотекста, содержащихся в шаблоне, на котором основан документ. При выборе элемента из списка, вставлять его в документ.
Как всегда, начнём с XML-схемы. На ленту поместим группу "Автотекст" с выпадающим списком и кнопкой для обновления этого списка, если при работе с документом мы добавим элемент автотекста шаблон. В качестве списка я буду использовать не comboBox, а dropDown. Они абсолютно идентичны, но comboBox позволяет принимать текст, введённый пользователем. Нам это не нужно, поэтому используем dropDown.
<?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 idMso="TabHome">
    <group id="grAutoText" label="Автотекст">
     <dropDown id="ddAutoText"
          onAction="ddAutoText_onAction"
          getItemLabel="ddAutoText_getItemLabel"
          getItemSupertip="ddAutoText_getItemSupertip"
          getItemCount="ddAutoText_getItemCount"
          getItemID="ddAutoText_getItemID"
          getSelectedItemIndex="ddAutoText_getSelectedItemIndex" >

     </dropDown>
     <button id="btnRefresh"
         label="Обновить"
         imageMso="RecurrenceEdit"
         onAction="btnRefresh_onAction" />
    </group>
   </tab>
  </tabs>
 </ribbon>
</customUI>

Пояснения к атрибутам элемента dropDown.

  • onAction — процедура, вызываемая при нажатии на пункте списка

  • getItemLabel — процедура для задания текста элемента списка

  • getItemSupertip — процедура для задания текста расширенной подсказки элемента списка

  • getItemCount — процедура для задания количества элементов списка

  • getItemID — процедура для задания идентификаторов элементво списка (необязательная)

  • getSelectedItemIndex — процедура для выбора определённого элемента списка


Кнопка совершенно обычная и никаких трудностей у вас не должна вызвать, если вы дошли до создания списка. Если же возникли трудности, то ознакомьтесь с этой заметкой. Сохраняем шаблон и открываем его в Word, не забыв перед этим получить заготовку для кода VBA. Для этого в программе Ribbon XML Editor, которую я настоятельно рекомендую к использованию, предусмотрена специальная функция : вы можете автоматически получить заготовки для всех динамических атрибутов, указанных в XML-схеме и сохранить их в виде модуля *.bas для последующего импорта в документ.
Теперь код VBA. Открываем шаблон в Word, импортируем сохранённый модуль с процедурами ленты и доводим его до такого состояния:
Option Explicit
Dim tmp As Template
Dim bb As BuildingBlocks
Dim MyRibbon As IRibbonUI

' (компонент: customUI, атрибут: onLoad), 2007
Sub RibbonLoading(ribbon As IRibbonUI)
  Set tmp = ActiveDocument.AttachedTemplate
  Set bb = tmp.BuildingBlockTypes(wdTypeAutoText).Categories("Общие").BuildingBlocks
  Set MyRibbon = ribbon
End Sub

'ddAutoText (компонент: dropDown, атрибут: onAction), 2007
Sub ddAutoText_onAction(control As IRibbonControl, selectedId As String, selectedIndex As Integer)
  bb(selectedIndex).Insert Where:=Selection.Range, RichText:=True
End Sub

'ddAutoText (компонент: dropDown, атрибут: getItemLabel), 2007
Sub ddAutoText_getItemLabel(control As IRibbonControl, index As Integer, ByRef label)
  label = bb(index + 1).Name
End Sub

'ddAutoText (компонент: dropDown, атрибут: getItemSupertip), 2007
Sub ddAutoText_getItemSupertip(control As IRibbonControl, index As Integer, ByRef superTip)
  superTip = bb(index + 1).Value
End Sub

'ddAutoText (компонент: dropDown, атрибут: getItemCount), 2007
Sub ddAutoText_getItemCount(control As IRibbonControl, ByRef count)
'  Dim tmp As Template
  count = bb.count
End Sub

'ddAutoText (компонент: dropDown, атрибут: getItemID), 2007
Sub ddAutoText_getItemID(control As IRibbonControl, index As Integer, ByRef id)
  id = bb(index + 1).Name
End Sub

'ddAutoText (компонент: dropDown, атрибут: getSelectedItemIndex), 2007
Sub ddAutoText_getSelectedItemIndex(control As IRibbonControl, ByRef index)
  index = 0
End Sub

'btnRefresh (компонент: button, атрибут: onAction), 2007
Sub btnRefresh_onAction(control As IRibbonControl)
  MyRibbon.InvalidateControl "ddAutoText"
End Sub

В результате получим вот такой симпатичный список

В конце, несколько замечаний. Как видно из кода, для выборки элементов автотекста используется только одна категория: "Общие". Если автотекст распределён по нескольким категориям, они не будут видны. Если нужно распределить по категориям, то либо создавать столько списков, сколько в шаблоне категорий автотекста, либо использовать не список, а динамическое меню. Естественно это потребует переделки кода.
Пример шаблона

суббота, 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 для флажка в этом случае не вызывается! Поэтому кнопка остаётся активной.

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

среда, 10 февраля 2010 г.

Как изменить стандартные вкладки и группы на ленте

В ribbon-интерфейсе не предусмотрена возможность изменения стандартных групп и вкладок. Можно добавить свою группу на стандартную вкладку, убрать стандартную группу, убрать стандартную вкладку. Добавить свою свои элементы в стандартную группу нельзя. Нельзя также поменять местами расположение групп во вкладке. Вернее, поменять местами можно в Office 2010, но эти настройки сохраняются глобально для всего приложения, а не для отдельного документа или шаблона. Чтобы скрыть вкладку, нужно указать для неё idMso и задать значение false атрибуту visible:
1 2 3 4 5 6 7 8 9 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <!--Скрытие вкладки "Рецензирование"--> <tab idMso="TabReviewWord" visible="false"/> </tabs> </ribbon> </customUI>
Аналогично можно убрать стандартную группу из вкладки. Например, группу "Буфер обмена" с вкладки "Главная":
1 2 3 4 5 6 7 8 9 10 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab idMso="TabHome"> <group idMso="GroupClipboard" visible="false"/> </tab> </tabs> </ribbon> </customUI>
Также можно добавить свою группу на стандартную вкладку и задать её расположение атрибутом insertBeforeMso или insertAfterMso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab idMso="TabHome"> <!-- Вставляем свою группу на вкладку "Главная" перед группой "Абзац" --> <group id="rxTabMain_gr1" label="Границы" insertBeforeMso="GroupParagraph"> <box id="rxTabMain_gr1_box1" boxStyle="horizontal"> <control idMso="BorderNone" showLabel="false" /> <control idMso="BordersAll" showLabel="false" /> <control idMso="BorderInside" showLabel="false" /> <control idMso="BorderOutside" showLabel="false" /> </box> <box id="rxTabMain_gr1_box2" boxStyle="horizontal"> <control idMso="BorderBottomWord" showLabel="false" /> <control idMso="BorderTopWord" showLabel="false" /> <control idMso="BorderLeftWord" showLabel="false" /> <control idMso="BorderRightWord" showLabel="false" /> </box> <box id="rxTabMain_gr1_box3" boxStyle="horizontal"> <control idMso="BorderInsideHorizontal" showLabel="false" /> <control idMso="BorderInsideVertical" showLabel="false" /> <control idMso="BorderDiagonalDown" showLabel="false" /> <control idMso="BorderDiagonalUp" showLabel="false" /> </box> <box id="rxTabMain_gr1_box4" boxStyle="vertical"> <control idMso="TableDrawTable" showLabel="false" /> <control idMso="TableEraser" showLabel="false" /> <control idMso="BorderColorPicker" showLabel="false" /> </box> <dialogBoxLauncher> <button idMso="BordersShadingDialogWord" /> </dialogBoxLauncher> </group> </tab> </tabs> </ribbon> </customUI>

вторник, 9 февраля 2010 г.

Создание вкладки с набором стандартных элементов управления

Если вас не по каким-либо причинам не устраивает стандартное расположение элементов управления на "ленте" Word 2007-2010, то эта заметка для вас. Может показаться, что для Word 2010 это не актуально, но, как вы убедитесь, это далеко не так. Элементы управления на ленту размещаются посредством редактирования XML-кода. Создайте пустой документ, сохраните его с расширением docx или docm, закройте его и откройте в программе Ribbon XML Editor (RXE). После открытия вы увидите пустую вкладку с названием 2007 (customUI.xml) (название вкладки можно увидеть в левом углу рабочей области). Попробуем создать вот такую вкладку со стандартными элементами управления границами таблиц: Сразу замечу, что создать такую группу стандартными средствами Word 2010 невозможно (по крайней мере я не сумел). У меня получился такой результат: Конечно, это не то, что нужно. Поэтому оставим стандартные средства редактирования ленты на совести их разработчиков и займёмся делом. Итак, перед нами в программе RXE пустое содержимое файла настройки интерфейса customUI.xml. Начинаем добавлять XML-код. RXE значительно упрощает этот процесс. Добавьте в рабочую область RXE такой код:
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> </ribbon> </customUI>
В первой строке расположена стандартная строка спецификации XML, определяющая версию XML и кодировку. Эта строка добавляется нажатием на кнопку . Тег customUI, начинающийся со второй строки и заканчивающийся на шестой, определяет пространство имён XML (XML namespace), в котором создаётся наш интерфейс. Этот тег добавляется кнопкой . Для интерфейса приложений Office существует общее пространство имён внутри каждого приложения: "http://schemas.microsoft.com/office/2006/01/customui". Благодаря этому пространству имён наш интерфейс может встраиваться в интерфейс приложения. Если вы заметили, то при добавлении кода с помощью кнопок панели управления курсор всегда располагается так, чтобы сразу начать вводить следующую часть кода. Кнопкой добавим тег ribbon, указывающий, что мы формируем содержимое ленты. Для этого тега будет автоматически проставлен атрибут startFromScratch="false", указывающий, что не нужно убирать стандартные элементы интерфейса: вкладки и панель быстрого доступа. Далее, добавляем вкладки кнопкой (тег tabs). При этом сразу добавятся теги tab и group, поскольку тег tabs может иметь дочерним тегом только тег tab; а тег tab, в свою очередь, может имеет только один дочерний тег: group. Если всё сделано правильно,то ваш код должен иметь такой вид:
1 2 3 4 5 6 7 8 9 10 11 12 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="rxTabTable" label="Таблицы"> <group id="rxTabTable_gr1" label="Настройка границ"> </group> </tab> </tabs> </ribbon> </customUI>
Наконец-то, мы дошли до того момента, когда результат настройки интерфейса можно просмотреть. Сохраните документ в RXE и откройте его в Word, нажав кнопку . Документ откроется в Word и после всех стандартных вкладок в конце появится ваша собственная вкладка "Таблицы" с пустой группой "Границы". Если вы ничего подобного не видите, значит где-то допущена ошибка и придётся всё сделать с самого начала. Теперь наполним нашу группу содержимым. При размещении элементов управления они располагаются в порядке следования снизу-вверх и слева-направо. Для изменения этого порядка, можно применять контейнеры box и buttonGroup. Откройте документ в RXE, поставьте курсор между начальным и конечным тегом group и вставьте контейнер box кнопкой . Для этого тега автоматически добавляются атрибуты id и boxStyle. Атрибут boxStyle определяет размещение дочерних элементов внутри контейнера и может иметь два значения: vertical и horizontal. Разместим в группе первые четыре стандартных кнопки: "Отключить границы", "Все границы", "Внутренние границы" и "Внешние границы":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="rxTabTable" label="Таблицы"> <group id="rxTabMain_gr1" label="Границы"> <box id="rxTabMain_gr1_box1" boxStyle="horizontal"> <control idMso="BorderNone" showLabel="false" /> <control idMso="BordersAll" showLabel="false" /> <control idMso="BorderInside" showLabel="false" /> <control idMso="BorderOutside" showLabel="false" /> </box> </group> </tab> </tabs> </ribbon> </customUI>
Внутри контейнера box я разместил четыре соответствующих стандартных кнопки с помощью тега control, который можно добавить кнопкой . Этот тег как раз и предназначен для добавления стандартных элементов управления. В атрибуте idMso указывается стандартный идентификатор элемента управления. Список всех стандартных идентификаторов для приложений Office 2007 можно скачать отсюда. Атрибут showLabel используется для отображения или скрытия текста элемента управления. Открыв этот документ в Word, мы увидим наши четыре стандартных кнопки расположенных в одну строку: Понажимав на кнопки вы можете убедиться, что они работают совершенно также, как и на стандартных вкладках. Аналогичным образом добавим ещё две строки по четыре кнопки в каждой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="rxTabTable" label="Таблицы"> <group id="rxTabMain_gr1" label="Границы"> <box id="rxTabMain_gr1_box1" boxStyle="horizontal"> <control idMso="BorderNone" showLabel="false" /> <control idMso="BordersAll" showLabel="false" /> <control idMso="BorderInside" showLabel="false" /> <control idMso="BorderOutside" showLabel="false" /> </box> <box id="rxTabMain_gr1_box2" boxStyle="horizontal"> <control idMso="BorderBottomWord" showLabel="false" /> <control idMso="BorderTopWord" showLabel="false" /> <control idMso="BorderLeftWord" showLabel="false" /> <control idMso="BorderRightWord" showLabel="false" /> </box> <box id="rxTabMain_gr1_box3" boxStyle="horizontal"> <control idMso="BorderInsideHorizontal" showLabel="false" /> <control idMso="BorderInsideVertical" showLabel="false" /> <control idMso="BorderDiagonalDown" showLabel="false" /> <control idMso="BorderDiagonalUp" showLabel="false" /> </box> </group> </tab> </tabs> </ribbon> </customUI>
Осталось добавить три кнопки, расположенные вертикально по правому краю группы, и маленький квадратик в правом нижнем углу группы, служащий для вызова диалогового окна "Границы и заливка". Три кнопки нужно также поместить в контейнер box, только для атрибута boxStyle нужно указать значение "vertical". Маленький квадратик, служащий для вызова диалогового окна, добавляется тегом dialogBoxLauncher (кнопка ), имеющим атрибут idMso для указания идентификатора диалогового окна. Результирующий код будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui"> <ribbon> <tabs> <tab id="rxTabTable" label="Таблицы"> <group id="rxTabMain_gr1" label="Границы"> <box id="rxTabMain_gr1_box1" boxStyle="horizontal"> <control idMso="BorderNone" showLabel="false" /> <control idMso="BordersAll" showLabel="false" /> <control idMso="BorderInside" showLabel="false" /> <control idMso="BorderOutside" showLabel="false" /> </box> <box id="rxTabMain_gr1_box2" boxStyle="horizontal"> <control idMso="BorderBottomWord" showLabel="false" /> <control idMso="BorderTopWord" showLabel="false" /> <control idMso="BorderLeftWord" showLabel="false" /> <control idMso="BorderRightWord" showLabel="false" /> </box> <box id="rxTabMain_gr1_box3" boxStyle="horizontal"> <control idMso="BorderInsideHorizontal" showLabel="false" /> <control idMso="BorderInsideVertical" showLabel="false" /> <control idMso="BorderDiagonalDown" showLabel="false" /> <control idMso="BorderDiagonalUp" showLabel="false" /> </box> <box id="rxTabMain_gr1_box4" boxStyle="vertical"> <control idMso="TableDrawTable" showLabel="false" /> <control idMso="TableEraser" showLabel="false" /> <control idMso="BorderColorPicker" showLabel="false" /> </box> <dialogBoxLauncher> <button idMso="BordersShadingDialogWord" /> </dialogBoxLauncher> </group> </tab> </tabs> </ribbon> </customUI>
А группа будет иметь такой вид: Т.е. наша группа приобрела законченный вид. Единственным её отличием от желаемого вида в начале поста, является то, что вкладка расположена в самом конце, а не перед вкладкой "Главная". Это можно изменить, добавив атрибут insertBeforeMso, задав ему значение "TabHome". С учётом этих изменений тег tab будет иметь такой вид:
5 <tab id="rxTabTable" label="Таблицы" insertBeforeMso="TabHome">
На этом, пожалуй, всё.

понедельник, 11 января 2010 г.

Вставка картинок в документ из выбранной папки

Я разработал шаблон, упрощающий процедуру вставки в документ изображений из указанной папки. При загрузке шаблона появляется дополнительная группа на вкладке «Вставка» Возможности:
  1. Выбор папки, из которой нужно часто вставлять изображения.
  2. Просмотр эскизов изображений в галерее.
  3. Обрабатываются (пока) только изображения форматов: "png", "jpg", "jpeg", "bmp", "gif".
  4. Всплывающая подсказка к каждому изображению содержит имя файла, размеры изображения и разрешение по горизонтали и по вертикали.
  5. Вставка изображений в заданном режиме: в текст или с обтеканием.
  6. Автоматическая вставка названия к изображению с нумерацией или без.
  7. Автоматическая вставка имени файла в название к изображению.
В раскрытом виде галерея может выглядеть так: Галерея имеет фиксированное количество столбцов (5), количество строк зависит от количества изображений в выбранной папке. Скачать шаблон можно отсюда С точки зрения работы с лентой, этот шаблон интересен несколькими моментами. Во-первых, в нём реализована загрузка на ленту не только изображений bmp, но и других форматов. Это стало возможным благодаря использованию функций GDI+. За основу взяты примеры для книги RibbonX: Customizing the Office 2007 Ribbon, которые можно скачать отсюда Дело в том, что для отображения на ленте Office понимает только один формат изображения IPictureDisp. Получить этот формат изображения из файла можно функцией LoadPicture. Но она может загружать только изображения bmp. В шаблоне используется похожая функция LoadImage, описанная в модуле GDIPlusAPI. Код модуля выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 Attribute VB_Name = "GDIPlusAPI" Option Private Module Option Explicit Public Declare Function GdiplusStartup Lib "gdiplus" (token As Long, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As GpStatus Public Declare Function GdipCreateBitmapFromFile Lib "gdiplus" (ByVal filename As Long, bitmap As Long) As GpStatus Public Declare Function GdipCreateHBITMAPFromBitmap Lib "gdiplus" (ByVal bitmap As Long, hbmReturn As Long, ByVal background As Long) As GpStatus Public Declare Function GdipDisposeImage Lib "gdiplus" (ByVal image As Long) As GpStatus Public Declare Function GdiplusShutdown Lib "gdiplus" (ByVal token As Long) As Long Public Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As PICTDESC, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long Public Declare Function GdipGetImageDimension Lib "gdiplus" (ByVal image As Long, Width As Single, Height As Single) As GpStatus Public Declare Function GdipGetImageWidth Lib "gdiplus" (ByVal image As Long, Width As Long) As GpStatus Public Declare Function GdipGetImageHeight Lib "gdiplus" (ByVal image As Long, Height As Long) As GpStatus Public Declare Function GdipGetImageHorizontalResolution Lib "gdiplus" (ByVal image As Long, resolution As Single) As GpStatus Public Declare Function GdipGetImageVerticalResolution Lib "gdiplus" (ByVal image As Long, resolution As Single) As GpStatus Public Declare Function GdipGetImageThumbnail Lib "gdiplus" (ByVal image As Long, ByVal thumbWidth As Long, ByVal thumbHeight As Long, thumbImage As Long, Optional ByVal callback As Long = 0, Optional ByVal callbackData As Long = 0) As GpStatus Public Declare Function GdipLoadImageFromFile Lib "gdiplus" (ByVal filename As String, image As Long) As GpStatus Public Enum GpStatus OK = 0 GenericError = 1 InvalidParameter = 2 OutOfMemory = 3 ObjectBusy = 4 InsufficientBuffer = 5 NotImplemented = 6 Win32Error = 7 WrongState = 8 Aborted = 9 FileNotFound = 10 ValueOverflow = 11 AccessDenied = 12 UnknownImageFormat = 13 FontFamilyNotFound = 14 FontStyleNotFound = 15 NotTrueTypeFont = 16 UnsupportedGdiplusVersion = 17 GdiplusNotInitialized = 18 PropertyNotFound = 19 PropertyNotSupported = 20 End Enum Public Type GUID Data1 As Long Data2 As Integer Data3 As Integer Data4(0 To 7) As Byte End Type Public Type PICTDESC Size As Long Type As Long hPic As Long hPal As Long End Type Public Type GdiplusStartupInput GdiplusVersion As Long DebugEventCallback As Long SuppressBackgroundThread As Long SuppressExternalCodecs As Long End Type Public Function LoadImage(ByVal strFName As String) As IPicture Dim uGdiInput As GdiplusStartupInput Dim hGdiPlus As Long Dim hGdiImage As Long Dim hBitmap As Long Dim imgThumb As Long Dim imgHeight As Single, imgWidth As Single uGdiInput.GdiplusVersion = 1 'Запускаем GDI+ If GdiplusStartup(hGdiPlus, uGdiInput) = OK Then 'Создаём изображение в памяти If GdipCreateBitmapFromFile(StrPtr(strFName), hGdiImage) = OK Then 'Получаем размеры изображения Call GdipGetImageDimension(hGdiImage, imgWidth, imgHeight) 'Делаем из изображения уменьшенное Call GdipGetImageThumbnail(hGdiImage, ItemWidth, ItemWidth * imgHeight / imgWidth, imgThumb) 'Указатель на изображение Call GdipCreateHBITMAPFromBitmap(imgThumb, hBitmap, 0) 'Конвертируем изображение в IPicture Set LoadImage = ConvertToIPicture(hBitmap) GdipDisposeImage hGdiImage End If GdiplusShutdown hGdiPlus Else MsgBox "Ошибка при загрузке GDI+!", vbCritical End If End Function Public Function ConvertToIPicture(ByVal hPic As Long) As IPicture Dim uPicInfo As PICTDESC Dim IID_IDispatch As GUID Dim IPic As IPicture Const PICTYPE_BITMAP = 1 With IID_IDispatch .Data1 = &H7BF80980 .Data2 = &HBF32 .Data3 = &H101A .Data4(0) = &H8B .Data4(1) = &HBB .Data4(2) = &H0 .Data4(3) = &HAA .Data4(4) = &H0 .Data4(5) = &H30 .Data4(6) = &HC .Data4(7) = &HAB End With With uPicInfo .Size = Len(uPicInfo) .Type = PICTYPE_BITMAP .hPic = hPic .hPal = 0 End With OleCreatePictureIndirect uPicInfo, IID_IDispatch, True, IPic Set ConvertToIPicture = IPic End Function
Загрузка изображений происходит в модуле RibbonCallbacks в процедуре getItemImage, которая, в свою очередь, определена в XML-схеме для галереи.
68 69 70 71 72 'galleryImagesFromFolder (компонент: gallery, атрибут: getItemImage) Sub getItemImage(control As IRibbonControl, index As Integer, ByRef image) If ImagesCount = 0 Then Exit Sub Set image = LoadImage(arImagePaths(index)) End Sub
В этой процедуре arImagePaths — массив с путями к файлам изображений, index — номер элемента в галерее. Во-вторых, на примере данного шаблона можно понять как работает механизм изменения состояния одних элементов ленты в зависимости от состояния других. Начнём с XML-схемы трёх флажков, расположенных в правой части группы, которые определяют правила вставки названия к вставляемому изображению.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 <!-- Разделитель группы --> <separator id="sep1" /> <checkBox id="chbInsertImageWithCaption" label="Вставлять название" supertip="Установите этот флажок, чтобы автоматически вставлять подпись к вставляемому рисунку." onAction="chb_onAction" /> <checkBox id="chbUsePathInCaption" label="Имя файла в названии" supertip="Установите этот флажок, чтобы подпись к вставляемому рисунку содержала имя файла рисунка." getEnabled="getEnabled" onAction="chb_onAction" /> <checkBox id="chbNumberImage" label="Нумеровать изображение" supertip="Установите этот флажок, чтобы автоматически нумеровать вставляемое изображение." getPressed="chb_getPressed" getEnabled="getEnabled" onAction="chb_onAction" />
У всех трёх флажков определён один общий динамический атрибут onAction, определяющий процедуру, которая выполняется при клике на соответствующем флажке. Двум последним флажкам задан атрибут getEnabled, определяющий процедуру, изменяющую активность этих флажков. Активность этих флажков меняется в зависимости от состояния первого флажка. И, наконец, у третьего флажка задан атрибут getPressed, определяющий процедуру, изменяющую состояние флажка. Теперь посмотрим, как это реализовано в VBA:
29 'galleryImagesFromFolder (компонент: gallery, атрибут: getEnabled) 30 'chbUsePathInCaption (компонент: checkBox, атрибут: getEnabled) 31 'chbUsePathInCaption (компонент: checkBox, атрибут: onAction) 32 'chbNumberImage (компонент: checkBox, атрибут: getEnabled) 33 Sub getEnabled(control As IRibbonControl, ByRef enabled) 34 Select Case control.ID 35 Case "chbUsePathInCaption" 36 enabled = chbInsertImageWithCaptionChecked 37 Case "chbNumberImage" 38 enabled = chbInsertImageWithCaptionChecked 39 Case Else 40 enabled = CBool(ImagesCount) 41 End Select 42 End Sub
Эта процедура при помощи селективного оператора определяет состояние какого элемента ленты нужно установить и присваивает ему значение соответствующей глобальной переменной.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 'chbInsertImageWithCaption (компонент: checkBox, атрибут: onAction) 'chbUsePathInCaption (компонент: checkBox, атрибут: onAction) 'chbNumberImage (компонент: checkBox, атрибут: getPressed) Sub chb_onAction(control As IRibbonControl, pressed As Boolean) Select Case control.ID Case "chbUsePathInCaption" chbUsePathInCaptionChecked = pressed Case "chbNumberImage" chbNumberImageChecked = pressed Case "chbInsertImageWithCaption" chbInsertImageWithCaptionChecked = pressed If Not myRibbon Is Nothing Then myRibbon.InvalidateControl "chbUsePathInCaption" myRibbon.InvalidateControl "chbNumberImage" Else MsgBox "Связь с пользовательским интерфейсом customUI утеряна. Попробуйте переоткрыть документ, или переподключить шаблон", vbInformation + vbOKOnly End If End Select End Sub
В этой процедуре, также с помощью селективного оператора, определяется флажок, на котором щёлкнул пользователь и состояние этого флажка, переданное параметром pressed, записывается в соответствующую глобальную переменную. Кроме того, если сработал первый флажок, то обновляется состояние остальных двух.
140 141 142 143 'chbNumberImage (компонент: checkBox, атрибут: getPressed) Sub chb_getPressed(control As IRibbonControl, ByRef returnValue) returnValue = chbNumberImageChecked End Sub
Здесь всё просто: состояние флажка, на которое ссылается переменная returnedValue, устанавливается в соответствии со значением глобальной переменной. Здесь не используется селективный оператор, поскольку эта процедура вызывается только для одного флажка. Замечания и пожелания приветствуются.