Опубликован: 13.09.2006 | Уровень: для всех | Доступ: свободно | ВУЗ: Тверской государственный университет
Лекция 2:

Программная работа с документами Word

Реализация множественного буфера, основанная на кнопке класса ComboBox

Инструментальные кнопки класса ComboBox могут быть одного из трех типов, задавая окно редактирования, выпадающий список и список с окном редактирования. Кнопкой типа DropDown - выпадающий список можно воспользоваться для реализации множественного буфера. Вместо того, чтобы показывать список элементов буфера в отдельной форме, во многих случаях предпочтительнее иметь в меню кнопку типа DropDown, при работе с которой раскрывается список, отображающий элементы буфера.

Заметьте, этот прием может быть полезен не только в данном конкретном примере, но и во многих случаях, когда приходится предъявлять пользователю некоторый список для того, чтобы он сделал в нем свой выбор. Кнопка меню, конечно же, компактнее, чем отдельная открывающаяся форма. Именно по этой причине такой способ работы с множественным буфером может быть предпочтительнее.

Хочу обратить Ваше внимание, что стандартная реализация фактически сочетает оба эти способа. В стандартной реализации буфер обычно появляется в виде плавающей формы, но когда окно формы, являющееся стыковочным окном, "причаливает" к одному из краев экрана, то форма превращается в обычную инструментальную панель, и элементы буфера отображаются уже в списке кнопки типа Dropdown. Лично я предпочитаю при работе со стандартным буфером представлять его в виде инструментальной панели, но это, конечно, дело вкуса. Что же касается собственной реализации, то я предпочел разделить оба эти способа.

Перейдем теперь к рассмотрению деталей реализации. Замечу, что представление самого буфера в виде коллекции остается неизменным в обеих реализациях. Теперь только вместо формы появится инструментальная панель с соответствующими кнопками. Наряду с кнопкой типа DropDown, которая будет реализовывать операцию вставки элемента буфера, на инструментальной панели будут расположены и обычные командные кнопки, реализующие другие операции над буфером. Кнопку DropDown необходимо создать программно. Конечно, программно можно создать и саму панель, и все кнопки, расположенные на ней. Но я предпочел сочетать работу руками и программную работу. Вот как выглядит созданная инструментальная панель MultBufferPanel:

Панель MultBufferPanel с кнопкой типа DropDown

Рис. 2.3. Панель MultBufferPanel с кнопкой типа DropDown

Две кнопки в первой группе 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.
Сергей Дмитриев
Сергей Дмитриев
Россия, Москва