Программная работа с документами Word
Инструментальная панель и кнопки
Там где макросы, там и кнопки. Понятно, что макросы, подобные тем, что были написаны нами для работы с буфером, всегда связываются с инструментальными кнопками. В тестовом документе я создал панель Buffers с кнопками, каждая из которых вызывает соответствующий макрос. Вот как выглядит эта панель в тестовом документе:
Хочу обратить Ваше внимание на то, что спроектированные мной кнопки имеют значок и текст. В тех случаях, когда создается кнопка, являющаяся вариацией стандартной кнопки, разумно копировать значок стандартной кнопки и добавлять текст, указывающий специфику собственной кнопки. Именно так я и поступал, копируя для своих кнопок значки стандартных кнопок Copy и Paste.
Множественный буфер
Долгожданной новинкой, появившейся в Office 2000, было введение буфера, в котором может храниться не единственное значение, а множество значений. Теперь буфер позволяет одновременно хранить до 16 различных объектов, что, несомненно, бывает крайне полезным в работе пользователя, когда часто приходится использовать в тексте повторяющиеся термины, фразы, рисунки.
Конечно, всегда появляется желание, что-либо улучшить. Я бы, например, предпочел иметь одновременно оба буфера - одиночный для одноразовых работ и множественный для тех объектов, которые стоит запоминать на достаточно долгий срок. Иногда мне не хватает 16 объектов для долговременного хранения, и хотелось бы расширить объем множественного буфера. Хотелось бы также иметь возможность удалять некоторые объекты из буфера, надобность хранения которых отпала. В общем, причин, по которым может возникнуть потребность в написании собственной реализации множественного буфера может быть много. Да и задача эта интересна с программистской точки зрения. Давайте напишем собственную реализацию множественного буфера.
Как всегда, нам нужно будет решить три вопроса: как представить такой буфер, как добавлять в него элементы и как переносить нужный элемент из буфера в точку вставки. Для множественного буфера возникают и новые задачи - чистка буфера, выделение нужного элемента, удаление отдельных элементов.
Начнем с рассмотрения вопроса о представлении множественного буфера. Понятно, что единственной переменной теперь не обойтись и потребуется некоторая структура данных. Если ограничиться буфером с фиксированным числом элементов, как это сделано в стандартной реализации, то наиболее подходящим вариантом был бы массив. Но я предпочитаю в подобных случаях динамические структуры данных, где можно не задумываться о размерах буфера и не вводить, тем самым, лишних ограничений. В VBA есть отличная динамическая структура данных - коллекция. Ею я и воспользуюсь для представления буфера. Вот как выглядит раздел объявлений глобальных переменных модуля MultBufferModule, в котором я буду размещать все процедуры, связанные с решением нашей задачи:
Option Explicit 'Множественный буфер можно задать массивом или динамической структурой 'Я предпочитаю использовать динамическую структуру Public MultBuffer As New Collection Public Elem As Range Public NumElem As IntegerЛистинг 2.5.
Переменная MultBuffer задает множественный буфер, а переменные Elem и NumElem мне понадобятся для работы с элементами, хранящимися в буфере.
Основной вопрос, который предстоит решить, как показать пользователю, элементы, хранящиеся в буфере? Очевидно, это должен быть некоторый список элементов. Но каково должно быть содержимое элементов, предъявляемых пользователю для показа? Проблема в том, что объекты, хранящиеся в буфере, могут быть достаточно сложными - длинные тексты, рисунки, Ole-объекты, комбинация текста и рисунков и так далее. Я выберу решение этой задачи, подсказанное стандартной реализацией, - там, где объект содержит текст, пользователю будет предъявлен начальный участок этого текста. Для графических объектов и им подобным - элементам документа Word, входящим в коллекции Shapes и InlineShapes, пользователю будет предъявлено слово "ОбъектN", где N будет задавать номер такого объекта.
Возникает еще одна проблема при отображении списка элементов, хранящихся в буфере. Неясно, с каким интерфейсным элементом следует связать этот список. Первое напрашивающееся решение состоит в том, чтобы связать список со специально спроектированной формой. При возникновении необходимости вставки элемента из буфера эта форма и будет отображаться. В этой форме можно программно формировать список элементов буфера, и в ней можно иметь командные кнопки, позволяющие выполнять не только вставку из буфера, но и другие операции над элементами буфера.
Другое возможное решение состоит в том, чтобы для отображения списка использовать панель инструментов со специальными кнопками, в частности, иметь на панели кнопку класса ComboBox типа DropDown. Эта кнопка также позволяет динамически формировать список ее элементов и задавать реакцию на выбор определенного элемента из списка. О работе с такими кнопками я расскажу в следующей лекции. Сейчас мы рассмотрим оба варианта реализации, поскольку оба они интересны с программистской точки зрения.
Реализация множественного буфера, основанная на форме
Прежде всего, я спроектировал форму с именем "BufferForm" для представления элементов, хранящихся в буфере. Эта форма, в полном соответствии с тем, что говорилось ранее, включает список, отображающий элементы буфера, и три командные кнопки. Взгляните, как выглядит эта форма в момент работы с ней:
Три командные кнопки, помещенные в форму, - "Вставить элемент", "Удалить элемент" и "Удалить все" - выполняют основные операции над элементами буфера. Отмечу одно важное свойство, установленное при проектировании формы BufferForm. Я сделал эту форму немодальной, установив булево свойство ShowModal как False. Это позволяет держать форму открытой при работе с документом и при необходимости вставлять нужные элементы из открытого списка.
Прежде, чем продолжить разговор о работе с этой формой, давайте займемся операцией добавления элементов в наш буфер. Вот как реализуется копирование выделенных объектов документа Word в наш множественный буфер. Приведу текст соответствующего макроса:
Public Sub CopyMult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range End SubЛистинг 2.6.
Как видите, в макросе копирования элементов в буфер для добавления элементов в коллекцию, используется метод Add, вся задача выполняется одним оператором и не требует особых пояснений. Заметьте, в коллекцию полностью добавляется выделенный объект, а не его текст или другие компоненты.
Конечно, в этот же момент можно было бы создавать и соответствующий элемент списка нашей формы. Но разумнее это делать в другом месте - в обработчике события, возникающего при активизации формы. Действительно, если представить, что форма уже открыта и расположена на экране, то программное добавление нового элемента в ее список не будет отображаться, пока форма не будет перерисована. Раз так, то целесообразнее каждый раз при активизации формы (появлении ее на экране) создавать список на основе текущего состояния множественного буфера - коллекции MultBuffer. Вот как выглядит для формы написанный мной обработчик события Activate, решающий задачу создания элементов списка формы. Естественно, что список создается на основе анализа состояния буфера:
Private Sub UserForm_Activate() Dim Txt As String NumElem = 0 For Each Elem In MultBuffer 'Анализ типа элемента буфера и создание элемента списка If Elem.Characters.Count = 1 Then 'Это объект Shape, InlineShape, 'специальный или однобуквенный символ! NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Elem.Text, 40) End If 'Добавление элемента в список формы ListBox1.AddItem Txt Next Elem End SubЛистинг 2.7.
В цикле по объектам коллекции, составляющим буфер, анализируется их тип, и создаются текстовые элементы, представляющие элементы списка нашей формы. Заметьте, я достаточно просто расправляюсь с проблемой распознавания типа объекта, хранящегося в буфере. Для распознавания я использую следующий факт, - для всех рисованных объектов, объектов WordArt и подобных им объектов документа Word, свойство текст задается одним специальным символом. Поэтому я применяю следующее правило, - все объекты буфера, для которых свойство Characters возвращает коллекцию символов, содержащую ровно один символ, относятся к нетекстовым объектам или не могут, я полагаю, быть распознанными пользователем по этому символу. Поэтому такие объекты должны отображаться в списке словом "Объект" с соответствующим номером. Под это определение подпадают объекты Shape и InlineShape, специальные символы и, конечно, просто однобуквенные символы. Конечно, можно было бы предусмотреть более сложный анализ, но и принятое решение представляется вполне разумным и вполне удовлетворительно, как мне кажется, на практике. Некоторое сомнение может возникать в тех случаях, когда действительно копируются однобуквенные символы, но, заметьте, специальные символы целесообразно считать объектами, а простые символы вряд ли стоить копировать в множественный буфер. Далее я все-таки покажу, как можно было бы более детально проанализировать тип объекта, хранящегося в буфере.
Итак, задача создания буфера и списка, отражающего его элементы, решена полностью. Давайте теперь рассмотрим работу по вставке элементов из буфера и другие операции, реализованные над этими элементами. Прежде всего, замечу, что, как обычно, я спроектировал специальную инструментальную панель "MultBufferPanel" и расположил на ней две командные кнопки: "CopyMult" и "PasteMult". В ответ на нажатие первой кнопки вызывается уже приведенный одноименный макрос "CopyMult". В обработчике события нажатия второй кнопки вызывается макрос "PasteMult". Текст его очень прост:
Public Sub PasteMult() 'Показывает форму с элементами буфера BufferForm.Show End SubЛистинг 2.8.
Заметьте, что при открытии формы возникает событие Activate, а посему будет вызван обработчик этого события, что приведет к инициализации списка формы, так что в открывшейся форме будет показан список элементов, отражающих текущее состояние буфера. Ввиду немодального характера формы она будет оставаться на экране в процессе работы пользователя с документом до тех пор, пока пользователь не сочтет нужным закрыть ее. В любой момент пользователь может выполнять операции над буфером, заданные командными кнопками формы. Выбрав некоторый элемент из списка формы, и нажав кнопку "Вставить элемент", пользователь получает возможность вставить выбранный элемент в позицию, указанную курсором. Вот текст соответствующих макросов, решающих эту задачу:
Private Sub CommandButton1_Click() InsertElem End Sub Public Sub InsertElem() 'Вставляет выбранный элемент буфера 'в точку, заданную курсором Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True Set Elem = MultBuffer(RowIndex + 1) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Exit For End If Next RowIndex If Not Sel Then MsgBox ("Для вставки выберете элемент из списка!") End If End With End SubЛистинг 2.9.
Несколько слов о том, как реализована вставка. Вначале я определяю, какой элемент списка выбран, что позволяет определить индекс элемента буфера (коллекции), который должен быть вставлен в позицию курсора. На этом этапе также приходится анализировать тип вставляемого объекта, поскольку объекты Shape не могут быть вставлены в позицию, заданную курсором. Более того, они не могут быть скопированы стандартным способом через буфер, для них приходится применять метод Duplicate, специально предназначенный для этих целей. Заметьте, в данной ситуации необходимо более корректно анализировать тип объекта, находящегося в буфере. Для распознавания того, что объект буфера принадлежит классу Shape, я вызываю метод RangeShape, возвращающий коллекцию Shapes объекта Range. Если эта коллекция не пуста, то имеем дело с объектом Shape.
Для остальных объектов, не принадлежащих классу Shape, вставка выполняется через стандартный буфер, о чем я уже говорил в предыдущем параграфе, где рассматривалась работа с одиночным буфером.
В стандартной реализации помимо вставки над буфером определены и другие операции, в частности, операция удаления элементов. Удалить можно одновременно все элементы буфера, либо удаление происходит по принципу стека. Поскольку множественный буфер имеет фиксированный размер, то при добавлении нового элемента в уже заполненный буфер, удаляется первый из его элементов, освобождая место вновь пришедшему. Наш буфер является безразмерным, поэтому нет необходимости в реализации стекового принципа удаления элементов. Но иметь возможность по своему выбору удалять уже не нужные элементы и в этом случае целесообразно. Вот макросы, решающие эту задачу:
Private Sub CommandButton2_Click() DelElem End Sub Public Sub DelElem() 'Удаляет выбранный элемент буфера Dim RowIndex As Integer Dim Sel As Boolean Sel = False With BufferForm.ListBox1 For RowIndex = 0 To .ListCount - 1 If .Selected(RowIndex) Then Sel = True MultBuffer.Remove (RowIndex + 1) .RemoveItem RowIndex Exit For End If RowIndex = RowIndex + 1 Next RowIndex If Not Sel Then MsgBox ("Для удаления выберете элемент из списка!") End If End With End SubЛистинг 2.10.
Заметьте, выбранный элемент удаляется как из списка, так и из буфера
Кнопка "Удалить все" позволяет полностью очистить буфер и, соответственно, список формы. По сути, макрос, решающий эту задачу, не многим отличается от макроса, удаляющего один элемент:
Private Sub CommandButton3_Click() ClearAll End Sub Public Sub ClearAll() 'Удаляет элементы из буфера (коллекции) Dim i As Integer For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаляет элементы из списка For i = 1 To BufferForm.ListBox1.ListCount BufferForm.ListBox1.RemoveItem (0) Next i BufferForm.Hide End SubЛистинг 2.11.
На этом закончим рассмотрение реализации множественного буфера, использующего форму для отображения элементов буфера. Рассмотрим другой возможный вариант реализации этой задачи.