суббота, 27 декабря 2008 г.

Обработка событий на уровне приложения.

При работе над совершенствованием и подгонкой под себя «ленточного» интерфейса я столкнулся с необходимостью реагировать на действия пользователя. В частности, при изменении выделенного блока текста.
Справка Word дает довольно скудные сведения об обработке событий. Вы можете в этом убедиться, запросив в «Справке» данные по теме Using Events with the Application Object.
Итак, что же нужно делать?
Во-первых, создать класс. Это делается в редакторе Visual Basic через меню Insert>Class Module. В этом классе нужно назначить ответственного за обработку событий. А затем для каждого необходимого события писать обработчик.
1 2 3 4 5 6 Public WithEvents App As Word.Application 'ответственный за события Private Sub App_WindowSelectionChange(ByVal Sel As Selection) 'Здесь пишем любой код, который должен выполняться. CustomRibbon.InvalidateControl "SetLineSpacing" 'обновление ленты при изменении выделенного диапазона. End Sub

Но это не все. Еще нужно указать в модуле, что для обработки событий мы используем свой собственный класс. Делается это так:
1 2 3 4 5 Dim MyEvents As New EventClassModule 'конструктор нашего класса Sub LoadRibbon(ribbon As IRibbonUI) Set MyEvents.App = Word.Application 'перенаправление событий приложения в объект нашего класса End Sub

Перенаправление событий можно делать в любом месте кода. Чаще всего это нужно делать при открытии документа, либо, как у меня, при загрузке ленты. В Справке Word об этом ничего не сказано. Пришлось доходить самому.
Более подробно об использовании этого события при работе с «ленточным» интерфейсом читаем здесь.

четверг, 25 декабря 2008 г.

Сохранение значений формы в переменных документа

В прошлой заметке я привел несколько макросов, разработанных самостоятельно, которые позволяют сохранять содержимое полей ввода при закрытии формы и восстанавливать их при открытии формы. Пояснять код нет необходимости — он достаточно хорошо раскомментирован. В пояснении, на мой взгляд, нуждаются предпосылки для реализации именно такого алгоритма.
Сохранение значений
Для хранения значений я использовал такое понятие как ActiveDocument.Variables или переменные документа.
Этим я преследовал единственную цель: все данные, которые необходимы для работы программы хранить в одном файле, что очень удобно с точки зрения конечного пользователя.
Работа с переменными документа довольно понятно расписана в справке по Visual Basic for Application. Я хотел бы сказать о другом. О принципе хранения значений.
Каждая переменная в документе получает свое имя (Name), причем не случайное, а содержащее имя компонента, значение которого она содержит. Если компонент содержит несколько значений, например, это список, то кроме имени компонента в имя переменной нужно записать и номер каждой записи списка. Т.е. каждая запись в списке хранится в своей переменной.
Я в приведенном примере пользовался такими именами
1 2 FatherName_ComboBox00 'хранение списка отчеств. Эта переменная хранит первый элемент списка FatherName_ComboBoxSelectedItem 'здесь хранится номер выбранной строки списка отчеств

Если нужно хранить логическое значение True или False, как например для CheckBox, то для этого просто записываем значение как текст.
Нюанс при работе с переменными.
При сохранении каждой переменной присваивается индекс, но не в порядке создания, а по правилам сортировки. У меня возникла ситуация, когда при количестве элементов списка более 10, после переменной с именем FatherName_ComboBox1 (индекс 0) сразу шла переменная FatherName_ComboBox10 (индекс 1), а не FatherName_ComboBox2 как хотелось бы. При чтении переменных это вызывало ошибку, т.к. происходила попытка записи в 10 строку списка, а строки такой еще не было.
Выход из этой ситуации был найден такой. Номер строки списка задавался не одной, двумя цифрами 00, 01 … и т.д. Это позволит без проблем сохранять список из 100 значений. Если нужно больше, то нужно соответственно увеличить количество цифр в номере.

среда, 24 декабря 2008 г.

Использование форм

Часто в документы нужно вносить изменения только в определенные места. Если это делать вручную, то это отнимает много времени, и при довольно большом документе трудно избежать ошибок.
В этом случае на помощь приходят формы. В качестве примера приведу форму, которую я делал по просьбе одного человека из Челябинска. Ему нужно было упростить работу по регистрации людей, которые проходят курсы повышения квалификации. При этом заполняется три документа, в которые вносятся одни и те же данные и затем документы печатаются. В среднем это занятие отнимало у человека 10-12 минут на каждого.
Итак, вот форма:

Ее заполнение занимает у человека порядка 10-12 секунд, еще 7 секунд на распечатку и документы готовы. Итого около 20 секунд на человека.
При разработке этой формы были решены некоторые вопросы, которые могут возникать при использовании таких форм другими людьми. А именно:

  1. Сохранение введенных значений имени, отчества и профессий.

  2. Автоматическое увеличение номера договора при открытии документа, или после его печати.

  3. Добавление или удаление значений из списков имен, отчеств и профессий

  4. Вставка фамилии, имени и отчества в дательном падеже

При разработке форм нужно чтобы имена компонентов, которые вы используете содержали имя закладки, в которую вносится информация из этого компонента, и тип этого компонента. Например, Date_TextBox.
Ниже привожу найденные мною решения.
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 Public Sub StoreVars() Dim StoredVar, fCtrl As Variant Dim Num, Num1, Num2, i As Integer 'Записываем значения полей в текстовые переменные For Each fCtrl In Me.Controls 'берем каждый контрол With fCtrl If TypeName(fCtrl) = "TextBox" Then 'если это текстовое поле For Each StoredVar In MainDoc.Variables If StoredVar.Name = .Name Then Num = StoredVar.Index 'определяем, есть ли переменная с именем как у контрола Next StoredVar If Num = 0 Then 'если переменная с таким именем не существует MainDoc.Variables.Add Name:=.Name, Value:=CapFirstLetter(.Value) 'создаем ее и присваиваем значение контрола Else 'если такая переменная уже есть MainDoc.Variables(Num).Value = CapFirstLetter(.Value) 'просто присваиваем ей значение Num = 0 End If End If 'Сохраняем содержимое списков с профессиями If TypeName(fCtrl) = "ComboBox" Then For i = 0 To .ListCount - 1 'Определяем существуют ли переменные для хранения элементов списков и запоминания номера строки выбранного элемента для каждого списка. For Each StoredVar In MainDoc.Variables If StoredVar.Name = .Name + Format(Str(i), "00") Then Num1 = StoredVar.Index If StoredVar.Name = .Name & "SelectedItem" Then Num2 = StoredVar.Index Next StoredVar 'Если нет переменной для хранения элемента списка If Num1 = 0 Then 'то добавляем ее и присваиваем значение элемента списка MainDoc.Variables.Add Name:=.Name + Format(Str(i), "00"), Value:=.List(i) 'Если есть, Else 'то просто присваиваем ей значение. MainDoc.Variables(Num1).Value = .List(i) Num1 = 0 End If 'Если нет переменной для хранения номера строки выбранного элемента, If Num2 = 0 Then 'то добавляем ее. MainDoc.Variables.Add Name:=.Name & "SelectedItem", Value:=.ListIndex 'Если есть, Else 'то присваиваем ей значение выбранной строки списка MainDoc.Variables(Num2).Value = .ListIndex Num2 = 0 End If Next i End If If TypeName(fCtrl) = "CheckBox" Then 'если это флажок For Each StoredVar In MainDoc.Variables If StoredVar.Name = .Name Then Num = StoredVar.Index 'определяем, есть ли переменная с именем как у контрола Next StoredVar If Num = 0 Then 'если переменная с таким именем не существует MainDoc.Variables.Add Name:=.Name, Value:=.Value 'создаем ее и присваиваем значение контрола Else 'если такая переменная уже есть MainDoc.Variables(Num).Value = .Value 'просто присваиваем ей значение Num = 0 End If End If End With Next fCtrl End Sub


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 Public Sub RestoreVars() Dim StoredVar, fCtrl As Variant Dim ControlToAssign As Object 'Заполнение формы сохраненными значениями If MainDoc.Variables.Count <> 0 Then 'если есть сохраненные значения For Each StoredVar In MainDoc.Variables If InStr(StoredVar.Name, "TextBox") <> 0 Then 'если переменная хранит значение из текстового поля Me.Controls.Item(StoredVar.Name).Value = StoredVar.Value 'записываем ее значение в соотв. поле ElseIf InStr(StoredVar.Name, "ComboBox") <> 0 And InStr(StoredVar.Name, "SelectedItem") = 0 Then 'если в имени переменной есть слово "ComboBox" Set ControlToAssign = Me.Controls.Item(Left(StoredVar.Name, Len(StoredVar.Name) - 2)) ControlToAssign.AddItem StoredVar.Value, Val(Right(StoredVar.Name, 2)) 'записываем соотв. значение списка ElseIf InStr(StoredVar.Name, "CheckBox") <> 0 Then Me.Controls.Item(StoredVar.Name).Value = StoredVar.Value End If Next StoredVar For Each fCtrl In Me.Controls 'Устанавливаем значение каждого списка в то, которое было сохранено в предыдущем сеансе. If TypeName(fCtrl) = "ComboBox" Then fCtrl.ControlTipText = "Сохранить введенную запись — Ctrl+Insert. Удалить — Ctrl+Delete." If fCtrl.ListCount <> 0 Then fCtrl.ListIndex = MainDoc.Variables(fCtrl.Name & "SelectedItem").Value 'Показываем стрелочку выпадающего списка If fCtrl.ListCount <> 0 And InStr(fCtrl.Name, "Name") = 0 Then fCtrl.ShowDropButtonWhen = fmShowDropButtonWhenAlways Else: fCtrl.ShowDropButtonWhen = fmShowDropButtonWhenNever End If End If Next fCtrl End If End Sub


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 'Процедура удаления или добавления строк в выпадающие списки. Public Sub RemoveAddEntities(ByVal ObjControl As MSForms.ComboBox, _ action As String, _ Optional ByVal Index As Integer, _ Optional ByVal ShowDropButton = True) Dim k As Integer 'счетчик в цикле Dim Item_ As String 'временная переменная для хранения имени переменной при удалении ее. With ObjControl Select Case action Case "Add" If .ListIndex = -1 And .Text <> "" Then 'и нет совпадений с уже имеющимся элементом в списке .Text = CapFirstLetter(.Text) '* добавляем элемент в оба списка, причем .AddItem .Text '*первую букву первого слова делаем прописной InputForm.MainDoc.Variables.Add .Name + Format(Str(.ListIndex), "00"), .Text .SelStart = 0 '*выделяем содержимое поля списка .SelLength = Len(.Text) '* End If Case "Remove" .RemoveItem Index 'удаляем этот элемент For k = Index To .ListCount - 1 InputForm.MainDoc.Variables.Item(.Name + Format(Str(k), "00")).Value = .List(k) 'затем корректируем значения переменных, Next k 'чтобы они соответсвовали значениям с списке InputForm.MainDoc.Variables.Item(.Name + Format(Str(.ListCount), "00")).Delete 'Удаляем ненужную переменную 'Если список остался пустым, то удаляем и переменную, хранящую значение выбранной строки If .ListCount = 0 Then Item_ = ObjControl.Name & "SelectedItem" InputForm.MainDoc.Variables.Item(Item_).Delete End If 'Дальше показываем в списке значение, следующее за удаленным, либо, если удалили последний элемент списка, 'то показываем предыдущий элемент списка. If Index <> .ListCount Then .ListIndex = Index Else: .ListIndex = .ListCount - 1 End If End Select If .ListCount = 0 Or ShowDropButton = False Then 'Показываем или удаляем стрелочку выпадающего списка .ShowDropButtonWhen = fmShowDropButtonWhenNever Else: .ShowDropButtonWhen = fmShowDropButtonWhenAlways End If End With End Sub 'Функция преобразования первой буквы в строке в прописную Public Function CapFirstLetter(ByVal NeededString As String) As String Dim FirstLetter, RestOfWord As String FirstLetter = Format(Left(NeededString, 1), ">") 'первая буква делается прописной If InStr(NeededString, " ") <> 0 Then 'если в строке есть пробел, т.е. строка состоит из двух или более слов, то 'справа в первом слове берем количество букв на одну меньше, чем в этом слове есть, т.е. 'берем первое слово без первой буквы RestOfWord = Format(Right( _ Left(NeededString, InStr(NeededString, " ") - 1), _ InStr(NeededString, " ") - 2), "<") 'и прибавляем к нему остаток от строки с пробелом RestOfWord = RestOfWord & " " & Right(NeededString, Len(NeededString) - InStr(NeededString, " ")) Else 'если пробела в строке нет, т.е. введено одно слово, то просто берем его без первой буквы RestOfWord = Format(Right(NeededString, Len(NeededString) - 1), "<") End If CapFirstLetter = FirstLetter & RestOfWord 'соединяем первую букву, которая уже большая, с остальной частью строки End Function
Скачать весь документ

вторник, 23 декабря 2008 г.

Использование макросов в Word

Существует множество ресурсов в сети, на которых обсуждается создание и применение макросов в Word. Конечно, я не собираюсь их обгонять и вообще как либо с ними соревноваться. В этом блоге я буду размещать свои макросы и наработки по работе с ними.
Приступим.
1 Sub ВзятьВКавычки()
2 With Selection
3 If Right(.Text, 1) = Chr(32) Or _
4 Right(.Text, 1) = Chr(13) Then
5 .MoveLeft wdCharacter, 1, wdExtend
6 End If
7 If .Text <> "" Then
8 .InsertAfter "»"
9 .InsertBefore "«"
10 End If
11 End With
12 End Sub


1 Sub СделатьРимскойЦифрой()
2 Dim a As String
3 With Selection
4 .MoveLeft Unit:=wdWord, Count:=1, Extend:=wdWord
5 a = Trim(.Text)
6 .Fields.Add Range:=Selection.Range, Type:=wdFieldEmpty, _
7 divserveFormatting:=False, Text:="= " + a + " \*ROMAN"
8 .MoveLeft Unit:=wdWord, Count:=1, Extend:=wdWord
9 a = Trim(.Text)
10 .TypeText a & " "
11 End With
12 End Sub


Усовершенствованный макрос находится здесь
1 Sub Формула_с_нумерацией()
2 With Selection.ParagraphFormat
3 .SpaceBeforeAuto = False
4 .SpaceAfterAuto = False
5 .FirstLineIndent = CentimetersToPoints(0)
6 End With
7 With Selection
8 .ParagraphFormat.TabStops.ClearAll
9 .ParagraphFormat.TabStops.Add Position:=(ActiveDocument.PageSetup.PageWidth - ActiveDocument.PageSetup.LeftMargin - ActiveDocument.PageSetup.RightMargin) / 2 _
10 , Alignment:=wdAlignTabCenter, Leader:=wdTabLeaderSpaces
11 .ParagraphFormat.TabStops.Add Position:=(ActiveDocument.PageSetup.PageWidth - ActiveDocument.PageSetup.LeftMargin - ActiveDocument.PageSetup.RightMargin) _
12 , Alignment:=wdAlignTabRight, Leader:=wdTabLeaderSpaces
13 .TypeText Text:=vbTab
14 .InsertStyleSeparator
15 .TypeBackspace
16 .TypeText Text:=vbTab & "("
17 .InsertCaption Label:="Формула", TitleAutoText:="InsertCaption1", _
18 Title:="", Position:=wdCaptionPositionAbove, ExcludeLabel:=1
19 .TypeText Text:=")"
20 .MoveLeft Unit:=wdWord, Count:=5
21 .InlineShapes.AddOLEObject ClassType:="Equation.3", LinkToFile:= _
22 False, DisplayAsIcon:=False
23 End With
24 End Sub