Программная работа с документами Word
Реализация множественного буфера, основанная на кнопке класса ComboBox
Инструментальные кнопки класса ComboBox могут быть одного из трех типов, задавая окно редактирования, выпадающий список и список с окном редактирования. Кнопкой типа DropDown - выпадающий список можно воспользоваться для реализации множественного буфера. Вместо того, чтобы показывать список элементов буфера в отдельной форме, во многих случаях предпочтительнее иметь в меню кнопку типа DropDown, при работе с которой раскрывается список, отображающий элементы буфера.
Заметьте, этот прием может быть полезен не только в данном конкретном примере, но и во многих случаях, когда приходится предъявлять пользователю некоторый список для того, чтобы он сделал в нем свой выбор. Кнопка меню, конечно же, компактнее, чем отдельная открывающаяся форма. Именно по этой причине такой способ работы с множественным буфером может быть предпочтительнее.
Хочу обратить Ваше внимание, что стандартная реализация фактически сочетает оба эти способа. В стандартной реализации буфер обычно появляется в виде плавающей формы, но когда окно формы, являющееся стыковочным окном, "причаливает" к одному из краев экрана, то форма превращается в обычную инструментальную панель, и элементы буфера отображаются уже в списке кнопки типа Dropdown. Лично я предпочитаю при работе со стандартным буфером представлять его в виде инструментальной панели, но это, конечно, дело вкуса. Что же касается собственной реализации, то я предпочел разделить оба эти способа.
Перейдем теперь к рассмотрению деталей реализации. Замечу, что представление самого буфера в виде коллекции остается неизменным в обеих реализациях. Теперь только вместо формы появится инструментальная панель с соответствующими кнопками. Наряду с кнопкой типа DropDown, которая будет реализовывать операцию вставки элемента буфера, на инструментальной панели будут расположены и обычные командные кнопки, реализующие другие операции над буфером. Кнопку DropDown необходимо создать программно. Конечно, программно можно создать и саму панель, и все кнопки, расположенные на ней. Но я предпочел сочетать работу руками и программную работу. Вот как выглядит созданная инструментальная панель MultBufferPanel:
Две кнопки в первой группе RangeShape и PasteMult позволяют работать с множественным буфером, основанным на первой его реализации. Вторая группа кнопок предназначена для работы с буфером, основанным на втором способе реализации. Кнопка Copy1Mult позволяет копировать выделенный объект в документе Word в буфер, основанный на рассматриваемой нами DropDown реализации. Вторая кнопка, созданная программно, имеет тип DropDown и используется для выбора и вставки нужного элемента буфера в позицию, заданную курсором. Кнопка DelAll предназначена для чистки буфера, а кнопка InsertAll для вставки всех элементов буфера в позицию, заданную курсором. Заметьте, иногда такая операция бывает полезной. Также как и в стандартной реализации, отсутствует операция, позволяющая удалять единственный выбранный элемент буфера. Причина этого понятна, - выбор элемента в списке кнопки однозначно определяет и операцию, выполняемую над этим элементом. Такой операцией является операция вставки выбранного элемента. Для удаления элемента потребовалось бы иметь еще одну кнопку типа DropDown.
Все кнопки на панели MultBufferPanel можно создать руками, что я и делал. Единственным исключением является кнопка DropDown, которую нужно создавать программно. Рассмотрим, как это делается. Приведу сейчас тексты всех используемых мной процедур:
Public Function ExistCommandBar(PanelName As String) As Boolean 'Возвращает True, если в коллекции CommandBars 'существует панель с именем PanelName Dim bar As CommandBar, Exist As Boolean Exist = False For Each bar In CommandBars If bar.name = PanelName Or bar.NameLocal = PanelName Then Exist = True Exit For End If Next bar ExistCommandBar = Exist End Function Public Sub AddPanel(PanelName As String) 'Добавляет и делает видимой панель с именем Panelname 'в коллекцию Commandbars 'Панель расположена вверху документа, 'не заменяет главное меню и не является временной If Not ExistCommandBar(PanelName) Then Call CommandBars.Add(name:=PanelName, Position:=msoBarTop, _ MenuBar:=False, Temporary:=False) End If CommandBars(PanelName).Enabled = True CommandBars(PanelName).Visible = True End Sub Public Sub CreateComboPanel() 'Создание панели с элементами класса CommandBarCombobox 'Создаем панель Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox AddPanel ("MultBufferPanel") Set Panel = CommandBars("MultBufferPanel") 'Добавляем на панель Combo кнопку типа DropDown - выпадающий список Set Ctrl = AddCustomCombo(Panel, "DropdownItem", msoControlDropdown) 'Указываем обработчик события при выборе элемента списка Ctrl.OnAction = "DropdownReaction" End Sub Public Function ExistControl(Panel As CommandBar, _ Capt As String) As Boolean 'Возвращает True, если в коллекции Controls ' панели с именем Panel существует элемент с заголовком capt Dim Ctrl As CommandBarControl, Exist As Boolean Exist = False For Each Ctrl In Panel.Controls If Ctrl.Caption = Capt Then Exist = True Exit For End If Next Ctrl ExistControl = Exist End Function Public Function AddCustomCombo(Panel As CommandBar, _ name As String, tip As Variant) As CommandBarComboBox 'Добавляет на панель элемент, тип которого задан параметром tip 'возвращая объект CommandBarComboBox в качестве результата Dim Ctrl As CommandBarComboBox If Not ExistControl(Panel, name) Then Set Ctrl = Panel.Controls.Add(Type:=tip) Ctrl.Caption = name End If Set AddCustomCombo = Panel.Controls(name) End FunctionЛистинг 2.12.
Основной здесь является процедура CreateComboPanel, при запуске которой будет создана панель с именем MultBufferPanel, если она не была уже создана ранее, и на эту панель будет помещена кнопка типа Dropdown. Обратите внимание, что в этой процедуре задается обработчик события, которому я дал имя DropdownReaction, вызываемый в ответ на выбор пользователем элемента списка. Этот обработчик и будет выполнять основную операцию по вставке элемента буфера в позицию заданную курсором. Текст его приведу чуть позже, а сейчас замечу, что саму работу по добавлению данной кнопки на панель выполняет функция AddCustomCombo, которая не только создает кнопку, но и присваивает ей заголовок (Caption) "DropdownItem".
Нам осталось рассмотреть процедуры, выполняющие основные операции над элементами буфера. Начну с копирования выделенного объекта в буфер. Вот текст соответствующего макроса:
Public Sub Copy1Mult() 'Этот макрос копирует выделенный объект в множественный буфер MultBuffer.Add Selection.Range 'Одновременно создается список элемента ComboBox 'на панели MultBufferPanel Dim Txt As String Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox If Selection.Range.Characters.Count = 1 Then NumElem = NumElem + 1 Txt = "Object" & NumElem Else: Txt = Left(Selection.Range.Text, 40) End If Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") 'Добавление элемента списка Ctrl.AddItem Txt End SubЛистинг 2.13.
Заметьте, в отличие от макроса RangeShape в данной реализации не только добавляется элемент в буфер, но его текстовый образ тут же динамически добавляется и в список кнопки DropdownItem.
Как я уже говорил, операция вставки элемента выполняется при выборе элемента из раскрывающегося списка кнопки DropdownItem. Имя макроса - обработчика этого события уже задано в момент создания кнопки. Так что мне остается только привести его текст:
Public Sub DropdownReaction() 'Обработчик кнопки меню - DropDown ComboBox Dim Ctrl As CommandBarComboBox Set Ctrl = CommandBars.ActionControl Set Elem = MultBuffer(Ctrl.ListIndex) If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If End SubЛистинг 2.14.
Обратите внимание, я использую свойство ListIndex, чтобы понять, какой элемент был выбран пользователем из списка. Остальные детали этой процедуры подробно описаны при рассмотрении предыдущей реализации.
Макрос DelAll выполняет чистку буфера и списка:
Public Sub DelAll() 'Удаляет элементы из буфера и DropDown списка на панели Dim i As Integer Dim Panel As CommandBar Dim Ctrl As CommandBarComboBox 'Удаляет элементы из буфера (коллекции) For i = 1 To MultBuffer.Count MultBuffer.Remove (1) Next i 'Удаление из списка Set Panel = CommandBars("MultBufferPanel") Set Ctrl = Panel.Controls("DropdownItem") For i = 1 To Ctrl.ListCount Ctrl.RemoveItem (1) Next i End SubЛистинг 2.15.
В отличие от предыдущей реализации добавлена еще одна операция, позволяющая вставлять в точку вставки одним махом все элементы, хранящиеся в буфере. Это бывает полезно, когда собирается некоторый единый текст, путем компиляции отдельных фрагментов. Вот текст соответствующего макроса:
Public Sub InsertAll() 'Вставка всех элементов из буфера в точку, 'заданную курсором. Shape-элементы вставляются, как обычно, 'не привязанные к фиксированной позиции. For Each Elem In MultBuffer If Elem.ShapeRange.Count > 0 Then 'Это объект Shape - он не привязан 'к фиксированному положению, не может быть 'помещен в точку, заданную курсором и копируется 'специальным методом Duplicate Elem.ShapeRange(1).Duplicate Else Elem.Copy Selection.PasteSpecial End If Next Elem End SubЛистинг 2.16.
На этом я завершу рассмотрение способов создания буфера.
Вариации на тему кодирования
Я хочу сейчас привести тексты пяти макросов, которые я постоянно использую в своей повседневной работе. Почти все они являются вариациями одной из классических задач, возникающих при работе с текстами. Эта задача, которую будем называть задачей о трансляции символов, формулируется следующим образом: "Дан текст - последовательность символов. Каждый символ текста требуется заменить строкой символов или в частном случае - одним символом". Вот несколько типичных ситуаций, приводящих к этой задаче:
- При работе с двуязычным текстом фрагмент текста на русском языке ошибочно набран в английской раскладке клавиатуры. Требуется его преобразовать в соответствии с русской раскладкой
- Обратная задача - фрагмент текста на английском языке ошибочно набран в русской раскладке клавиатуры
- Задача транслитерации часто возникает при посылке за рубеж сообщений по Email русскоязычным абонентам, у которых нет кириллицы, и приходится русский текст набирать латинскими буквами. Существует общепринятые соглашения на кодировку символов. Большинство символов русского алфавита кодируются символами английского алфавита, но некоторые из них кодируются последовательностью из двух - трех символов.
- Наиболее распространенная вариация задачи этого типа связана с различными способами кодирования и декодирования символов. Например, Вы можете создать для переписки с абонентами собственный способ кодирования - декодирования, не защищающий, конечно от грубого взлома, но предохраняющий от простого любопытства. На эту тему написано большое число кодировщиков.
Прежде чем приводить реализацию частных случаев скажем несколько слов об общей схеме решения задачи трансляции. Для ее решения достаточно построить два массива одинаковой длины Source и Dest, первый - массив исходных символов, требующих перевода, второй - массив строк, задающих перевод каждого символа. Dest(i) задает перевод символа Sourse(i). Разумно задавать Source в виде строки символов, упорядоченных в соответствии с их кодировкой. Если кодирование имеет тип "символ в символ", то и Dest представляется строкой символов. Пусть теперь Text - это исходный текст, подлежащий переводу, а TextResult - результирующий текст после первода. Алгоритм решения задачи трансляции выглядит следующим образом:
TextResult = "" For Each Sym In Text Index = FindIndex(Sym, Source) TextResult = TextResult & Dest(Index) NextЛистинг 2.17.
Функция FindIndex определяет индекс вхождения символа Sym в строку Source. Эффективная ее реализация может использовать классический алгоритм бинарного поиска, имеющий сложность logM, где M - длина строки Source (количество символов исходного множества). Общая сложность алгоритма трансляции символов - NlogM, где N - длина текста. Учитывая, что, как правило, M < 256, общая сложность не превосходит 8N. Поиск индекса можно реализовать проще и эффективнее бинарного при условии, что исходное множество символов имеет плотную кодировку, то есть код следующего символа на 1 больше предыдущего. В этом случае используется встроенная функция Asс, возвращающая код символа.
Корректировка текста, набранного в "ошибочной" раскладке
Замечу, что для решения этой задачи в Office 2000 введена специальная "интеллектуальная" функция, которая распознает ошибку переключения клавиатуры. К сожалению, она правильно работает лишь в тех случаях, когда текст абзаца набирался на одном языке, а затем по ошибке произошло переключение клавиатуры на другой язык. В тех же случаях, когда текст является двуязычным, и каждый абзац может содержать фрагменты текста, например, термины на другом языке, эта функция работает некорректно. Мне она не подходит, я ее отключаю и пользуюсь собственными макросами.
При решении этой задачи я исходил из стандартной клавиатуры, имеющей 101 клавишу. Четыре ряда основных клавишей, используемых для набора русского и английского текста, содержат в двух регистрах 94 символа, не считая символов пробела и табуляции. Цифры и еще 10 символов одинаковы в обеих раскладках, а 74 символа нуждаются в трансляции, когда текст набран не в той раскладке.
Перевод текста из английской раскладки в русскую
Если русский текст набирается в английской раскладке, то при трансляции такого текста все буквы английского алфавита переходят в буквы русского алфавита. Но поскольку русских букв больше, то 14 небуквенных символов английской раскладки транслируются в русские буквы (7 в верхнем и 7 в нижнем регистрах). Кроме того, 7 символов, отличных от буквенно-цифровых, несовпадающие в разных раскладках, также должны быть переведены соответствующим образом.
При написании макроса я сочетал приведенную выше схему трансляции для буквенных символов английского алфавита, имеющих плотную кодировку, и схему разбора случаев для оставшихся символов. Посимвольный разбор случаев нагляден, хотя и не эффективен. В данном случае на некоторую потерю эффективности можно пойти, поскольку макросы применяются, обычно, к сравнительно небольшим текстам. Пришлось также учесть три нюанса, связанных с автоматической коррекцией текста в процессе его набора.
Первый из них таков. В английской раскладке русская буква "э" (большая и малая) задается кавычками - одинарными и двойными. Но тут-то и возникает закавыка, поскольку Word может автоматически заменять прямые кавычки, на другие "изящные" и угловые кавычки, причем кавычки могут быть как открывающие, так и закрывающие. Заменять прямые двойные кавычки могут четыре различные парные кавычки:
Для одинарных кавычек таких замен две. Так что при переводе необходимо учесть, что букве "э" могут соответствовать символы с разной кодировкой (пять или три в зависимости от верхнего или нижнего регистра).
Второй нюанс связан с набором символа "запятая". В английской раскладке этому символу соответствует вопросительный знак "?". Поскольку после запятой в русском тексте, как правило, следует пробел, то эта пара символов в английской раскладке воспринимается как конец вопросительного предложения. Слово, стоящее за запятой, воспринимается редактором Word как начало нового предложения и автоматически корректируется, - его первая буква становится большой и переводится в верхний регистр. При трансляции эту ситуацию необходимо обнаружить и вернуть соответствующий символ в нижний регистр.
Третья ситуация, требующая корректировки, похожа на вторую, но немного сложнее. Букве "ю" соответствует символ "точка". Поэтому, если "ю" заканчивает слово и за ним следует пробел, то символ, следующий за пробелом, Word будет автоматически преобразовывать в верхний регистр, воспринимая символ, как начало предложения. Следовательно, в такой ситуации необходима корректировка с возвращением соответствующего символа в нижний регистр. Но, если символ, следующий за "ю", не является пробелом, то автоматической коррекции не будет.
Несколько слов о переводе буквы "ё". Эта буква не входит в плотную кодировку русского алфавита и, зачастую, ее не рекомендуется использовать при наборе текстов. Тем не менее, я не исключаю возможности ее появления в тексте.
Приведем теперь текст макроса FromEToR, переводящего "английский ошибочный" текст в правильный русский:
Public Sub FromEToR() 'Translation of Symbols: England --> Russian Const ALU = "ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ" Const AL = "фисвуапршолдьтщзйкыегмцчня" Dim Sym As String, Sym1 As Range Dim Index As Byte Dim Result As String Dim Pravka As Boolean Dim Pravka1 As Boolean Pravka = False Pravka1 = False Result = "" For Each Sym1 In Selection.Characters Sym = Sym1 'Исправление ошибочной автокорректировки If Pravka And (Sym <> " ") Then Sym = LCase(Sym): Pravka = False Select Case Sym Case "A" To "Z" 'английская буква верхнего регистра Index = Asc(Sym) - Asc("A") + 1 Sym = Mid(ALU, Index, 1) Case "a" To "z" 'английская буква нижнего регистра Index = Asc(Sym) - Asc("a") + 1 Sym = Mid(AL, Index, 1) 'Символы, переходящие в символы Case "?": Sym = "," Case "/": Sym = "." Case "^": Sym = ":" Case "$": Sym = ";" Case "&": Sym = "?" Case "@": Sym = """" Case "#": Sym = "№" 'Символы, переходящие в буквы Case ",": Sym = "б" Case "<": Sym = "Б" Case ".": Sym = "ю" Case ">": Sym = "Ю" Case ";": Sym = "ж" Case ":": Sym = "Ж" Case "'": Sym = "э" Case """": Sym = "Э" Case "[": Sym = "х" Case "]": Sym = "ъ" Case "{": Sym = "Х" Case "}": Sym = "Ъ" Case "`": Sym = "ё" Case "~": Sym = "Ё" 'Другие виды кавычек Case Chr(145): Sym = "э" Case Chr(146): Sym = "э" Case Chr(147): Sym = "Э" Case Chr(148): Sym = "Э" Case Chr(171): Sym = "Э" Case Chr(187): Sym = "Э" Case Else: 'Кодировки совпадают End Select 'Обнаружение ошибочной автокорректировки If Sym = "," Then Pravka = True If Pravka1 And (Sym = " ") Then Pravka = True Else: Pravka1 = False End If If Sym = "ю" Then Pravka1 = True 'Формирование результата Result = Result + Sym Next Selection.LanguageID = wdRussian Selection.TypeText Result End SubЛистинг 2.18.