четверг, 12 марта 2015 г.

Урок 11. Создание второй надстройки, дополняющей первую.

На этом уроке мы попробуем написать вторую надстройку, которая могла бы использоваться как независимо от первой, так и располагаться вместе с ней на той-же вкладке. Пусть это будет надстройка работы со строками.

Создадим в Word новый документ и сохраним его как документ с макросами (.doсm). Убедимся, что в новом документе работает надстройка, сделанная нами на прошлом уроке. Закроем документ, и откроем его через Ribbon XML Editor.

Кстати, документы можно открывать в Ribbon XML Editor как непосредственно из этого редактора, так и из контекстного меню самого документа, что очень удобно. Но для этого нужно вначале добавить в эти контекстные меню соответствующий пункт. Это легко делается со страницы настроек Ribbon XML Editor.

Откройте вкладку настроек, и обратите внимание на раздел в правой части «Добавление «Открыть в Ribbon XML Editor» в контекстное меню проводника». Отметьте галочками те документы, для которых в контекстное меню проводника должен быть добавлен соответствующий пункт. В нашем случае достаточно одной галочки в столбце «Для файлов Word» напротив «Документ с макросами», но можно отметить и все галочки. Затем нажмите кнопку «Установить».

Обязательно прочтите всплывающую подсказку на кнопке, там есть много полезной информации. В частности, то, что документ будет открываться в той копии Ribbon XML Editor, из которой была осуществлена установка. Перед удалением программы, для того, чтобы убрать ненужные уже пункты контекстных меню, не забудьте снять все установленные галочки и снова нажать кнопку «Установить» для установки отменённого состояния пунктов контекстного меню.

Напишем интерфейс, аналогичный прежней надстройке:

<?xml version="1.0" standalone="yes"?>
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui">
    <ribbon startFromScratch="false">
        <tabs>
            <tab id="Вкладка1" label="Полезные надстройки" insertBeforeMso="TabHome" keytip="Н">
                <group id="РаботаСоСтроками" label="Работа со строками">
                    <button 
                        id="ДублироватьТекущуюСтроку" 
                        onAction="ДублироватьТекущуюСтроку" 
                        label="Дублировать" 
                        keytip="Д" 
                        imageMso="QuickStylesSets" 
                        size="large" 
                        screentip="Дублировать текущую строку" 
                        supertip="Сопировать текущую строку в строку ниже"/>
                    <button 
                        id="УдалитьСдвоенныеПустыеСтроки" 
                        onAction="УдалитьСдвоенныеПустыеСтроки" 
                        label="Удалить повторные пустые строки" 
                        keytip="С" 
                        imageMso="RecordsCollapseAllSubdatasheets" 
                        size="large" 
                        screentip="Удалить повторные пустые строки" 
                        supertip="Найти и заменить все повторяющиеся пустые строки одной"/>
                    <button 
                        id="УдалитьПустыеСтроки" 
                        onAction="УдалитьПустыеСтроки" 
                        label="Удалить пустые строки" 
                        keytip="В" 
                        imageMso="GroupQuerySetup" 
                        size="large" 
                        screentip="Удалить все пустые строки" 
                        supertip="Найти и удалить все пустые строки"/>                            
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>

Сгенерируем процедуры обратного вызова и сохраним их в файле. Запустим документ на выполнение. Ожидаем, что на уже существующую вкладку «Вкладка1» добавится новая группа. Попробуем запустить документ (F9). Запустили? Вот те на…

Вместо того, чтобы группа добавилась на вкладку с указанным идентификатором, мы увидели, что создалась ещё одна вкладка с тем же именем, куда и была помещена новая группа! Что же произошло не так?

По всей видимости, приложения офиса всё же различают внутри себя одинаковые идентификаторы. Очевидно, им автоматически присваиваются разные пространства имён (о которых мы говорили на первых наших уроках). Какой же из этого выход? Принудительно присвоить нашим идентификаторам одинаковое пространство имён. Для этого нам придётся произвести небольшую модификацию кода в обеих надстройках и вспомнить, что такое idQ.

Закроем Word, и удалим нашу прежнюю надстройку из папки:

    C:\Users\[ИмяПользователя]\AppData\Roaming\Microsoft\Word\STARTUP

В текущей надстройке, открытой в Ribbon XML Editor, добавим в тег customUI второй атрибут xmlns с указанием идентификатора нашего собственного пространства имён, например, МПИ (Моё Пространство Имён), и присвоим значение этому идентификатору, например, http://customui.blogspot.ru (интернет-адрес этого блога), как показано в строке ниже:

    xmlns:МПИ="http://customui.blogspot.ru"

Этим самым мы объявим новое пространство имён, в дополнение к пространству по умолчанию, которое выражалось строкой

    xmlns="http://schemas.microsoft.com/office/2006/01/customui"

Теперь в теге tab заменим атрибут id на атрибут idQ, чтобы иметь возможность включить в идентификатор вкладки префикс пространства имён, и перед идентификатором «Вкладка1» вставим этот префикс нашего нового пространства. Замечу, что как только мы добавляем новое пространство имён в тег интерфейса (customUI), оно сразу появляется в автодополнении, поэтому вставку префикса мы можем осуществить прямо из него. Итак, мы получили строку:

<tab idQ="МПИ:Вкладка1" insertBeforeMso="TabHome" keytip="Н" label="Полезные надстройки">

Запускаем документ на выполнение (F9) и видим нашу вкладку в интерфейсе. Она будет единственной, потому что старую нашу надстройку, которую мы ещё не модифицировали аналогичным образом, мы удалили. Переходим в редактор Бейсика (Alt+F11) и открываем в нём ранее сохранённые нами шаблоны процедур обратного вызова для этой надстройки. Затем заполняем их следующим образом:

'НайтиИЗаменить (компонент: button, атрибут: onAction), 2007
Sub НайтиИЗаменить(findString As String, replaceString As String)
    With Selection.Find
        .ClearFormatting
        .Replacement.ClearFormatting
        .Text = findString
        .Replacement.Text = replaceString
        .Forward = True
        .Wrap = wdFindContinue
        .Format = False
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
        .Execute Replace:=wdReplaceAll
    End With
End Sub

'ДублироватьТекущуюСтроку (компонент: button, атрибут: onAction), 2007
Sub ДублироватьТекущуюСтроку(control As IRibbonControl)
    With Selection
        .HomeKey Unit:=wdLine
        .MoveDown Unit:=wdLine, Count:=1, Extend:=wdExtend
        .Copy
        .HomeKey Unit:=wdLine
        .PasteAndFormat (wdFormatOriginalFormatting)
    End With
End Sub

'УдалитьСдвоенныеПустыеСтроки (компонент: button, атрибут: onAction), 2007
Sub УдалитьСдвоенныеПустыеСтроки(control As IRibbonControl)
Dim NumCharsBefore As Long, NumCharsAfter As Long
    Do
        NumCharsBefore = ActiveDocument.Characters.Count
        Call НайтиИЗаменить("^p^p^p", "^p^p")
        NumCharsAfter = ActiveDocument.Characters.Count
    Loop Until NumCharsBefore = NumCharsAfter
End Sub

'УдалитьПустыеСтроки (компонент: button, атрибут: onAction), 2007
Sub УдалитьПустыеСтроки(control As IRibbonControl)
Dim NumCharsBefore As Long, NumCharsAfter As Long
    Do
        NumCharsBefore = ActiveDocument.Characters.Count
        Call НайтиИЗаменить("^p^p", "^p")
        NumCharsAfter = ActiveDocument.Characters.Count
    Loop Until NumCharsBefore = NumCharsAfter
End Sub

В этих функциях мы реализовываем функционал кнопок. Мы можем увидеть здесь уже знакомую нам по первой надстройке функцию «НайтиИЗаменить», а также функции обратного вызова для кнопок. Функция дублирования строки создана методом записи макроса с последующей небольшой корректировкой, а две оставшиеся функции в цикле меняют одно количество символов абзаца (^p) на другое, в зависимости от задачи.

Замечу, что предложенная реализация функций не является эталонной. Напротив, это первое и самое простое, что пришло в голову. Напомню, что задачей этих уроков является построение интерфейса, а не программирование на VBA.

Сохраним код, перейдём в окно документа и проверим работу кнопок. Если всё работает так, как надо, то сохраняем документ как шаблон с поддержкой макросов (.dotm) в папку:

    C:\Users\[ИмяПользователя]\AppData\Roaming\Microsoft\Word\STARTUP

Эта надстройка готова. В Word пока не включаем её, чтобы не мешалась. Теперь открываем в Ribbon XML Editor документ со старой надстройкой, и правим её аналогично новой. Добавляем то же самое пространство имён, у вкладки меняем атрибут id на idQ и добавляем наш префикс перед идентификатором. Запускаем документ (F9), проверяем функционал и сохраняем его как шаблон рядом со второй надстройкой в папке

    C:\Users\[ИмяПользователя]\AppData\Roaming\Microsoft\Word\STARTUP

Закрываем сохранённый шаблон в Word и открытый документ в Ribbon XML Editor. Обе надстройки готовы к работе. Запускаем Word, лезем в настройки, и включаем обе надстройки, установив напротив них галочки. После сохранения изменений в настройках у нас появляется одна вкладка, содержащая две группы, сформированные разными надстройками! То, что нам и было нужно.

24 комментария:

  1. А можно ли как-то регулировать порядок групп на вкладке? Чтобы группа из второй надстройки располагалась после, а не до группы из первой надстройки.

    ОтветитьУдалить
    Ответы
    1. Ну вообще, у групп тоже есть атрибуты insertBeforeMso и insertAfterMso.

      Удалить
    2. А также insertBeforeQ и insertAfterQ.

      Удалить
    3. А в чём фишка такой группировки? Не проще использовать в той же надстройке в XML-коде
      - для создания групп в той же вкладке?
      Использовать при надобности "Separator".

      А можете написать пример записи - insertBeforeQ и insertAfterQ в XML-коде.

      Удалить
    4. - для создания групп в той же вкладке? использовать тэг "group".
      (этот блог удаляет записи походу).

      Удалить
    5. ыра, Как я понимаю, фишка в том, что надстройки для одной вкладки могут писаться разными людьми и в разное время.

      Удалить
    6. То есть если я для себя делаю надстройку то этот урок мне и не нужен правильно?

      Удалить
    7. Ну бывает так, что вы не хотите трогать первую надстройку по какой-то причине. Тогда делаете вторую, которая просто дополняет первую. Вариантов уйма может быть. Вы сами решаете, нужна вам эта возможность в каждом конкретном случае, или нет.

      Удалить
  2. Что-то не получается. А какие значения необходимо задать этим атрибутам?

    ОтветитьУдалить
    Ответы
    1. Каким именно атрибутам? В статье приведён полный код. А в xml-разметке в качестве значения атрибута просто прописывается имя функции.

      Удалить
  3. Не добавляет пункты в "Конт. Меню". Появляется такое окно - http://tiny.cc/7rxm6y
    Файл - "RibbonXMLEditor_AddInContextMenu.exe" имеется и он лежит рядом с "RibbonXMLEditor.exe".

    ОтветитьУдалить
    Ответы
    1. Напишите мне на почту (мейл указан в программе). Вышлю патченый файл. А старую версию можно попробовать заставить работать так: откройте в проводнике корневую папку программы и запустите оттуда RibbonXMLEditor.exe. То есть, смысл такой — директория по умолчанию должна быть корневой директорией папки программы. Должно сработать. Напишите мне, сработало ли.

      Удалить
  4. Добрый вечер!
    Подскажите, как с помощью программы выполнять макрос https://www.planetaexcel.ru/techniques/3/45/
    Просто вставить в надстройку и добавить процедуру вызова - ничего не вышло, создается только шапка списка.
    Если тема обсуждалась - прошу направить.
    Спасибо!

    ОтветитьУдалить
    Ответы
    1. А не из надстройки, а просто, как макрос, вы его смогли запустить?
      А из надстройки просто генерите шаблон обратного вызова и вставляете в него вызов этого макроса.

      Удалить
    2. Да, как простой макрос - все работает.

      Как только вставляю макрос между
      Sub FileList(control As IRibbonControl)
      End Sub
      - отрабатывает только первая часть, до процедуры ListFilesInFolder (как я понял, к ней обращается основной макрос при переборе файлов в папке и подпапках).

      Или я что-то делаю не так?

      Удалить
    3. А процедура ListFilesInFolder находится в том же модуле, рядом с FileList?
      Я бы сначала вызвал FileList() просто из сгенерированной процедуры обратного вызова, а процедуры FileList и ListFilesInFolder положил бы рядом с этой процедурой без изменений.

      Удалить
    4. Да, процедура в том же модуле.
      Не понял идею "процедуры FileList и ListFilesInFolder положил бы РЯДОМ с этой процедурой без изменений."
      Может быть такое, что не поддерживается рекурсия?

      Удалить
    5. Ну это роли не играет, но попробуйте вызвать FileList() из процедуры обратного вызова, а не делать сам FileList() процедурой обратного вызова.
      Если макрос работает прямым вызовом, то он должен работать и через кнопку.

      Удалить
    6. Спасибо огромное! Изначально не понял, что процедура обратного вызова нужна для вызова макроса, сам макрос в нее не нужно запихивать.
      Теперь все работает, всего вам наилучшего!!!

      Удалить
    7. Рад, что всё получилось. Вообще, основную процедуру макроса можно оформить и как процедуру обратного вызова, но вы, видимо, при этом что-то сделали неправильно. А сейчас сам макрос не подвергается никакой модификации вообще, поэтому вероятность ошибки снижается.

      Удалить
  5. При использовании idQ вместо id, для этого элемента пропадает возможность подгружать значения атрибутов из VBA (getVisible перестаёт работать). Можно ли это как-то обойти?

    ОтветитьУдалить
    Ответы
    1. Я не спец в этом, но вообще-то такого не должно быть... Странно. Если найдёте причину или решение, сообщите пожалуйста!

      Удалить