2 Курс Информатика VBA(ЗО) / Книги / В.Д.Хорев - Самоучитель программирования на VBA в Microsoft Office
.pdfСоздание приложения Registrator 253
•Если при возникновении такой ситуации щелкнуть на кнопке Debug окна сообщения и подвести указатель к переменной CurMsg, то в строке всплывающей подсказки, которая появится спустя несколько секунд, можно увидеть, что переменная CurMsg имеет значение Nothing (рис. 9.18), т.е. что она не была инициализирована.
•Дело в том, что в папке отправленных сообщений могут храниться не только обычные сообщения, который в Outlook 2000 представляются объектами класса MailItem, но и уведомления о получении сообщений, которым в объектной модели Outlook соответствует класс ReportItem. Поэтому, естественно, система не сможет, получив из коллекции Items объекта CurFolder ссылку на экземпляр класса ReportItem и присвоить это значение объектной переменной класса MailItem, которая в рассматриваемой процедуре представлена переменной CurMsg.
Рис. 9.18. Система не смогла создать экземпляр объекта класса MailItem
•Для того чтобы обойти это ограничение и используется переменная FolderItem класса
Object. Так как класс Object является универсальным, с инициализацией этой переменной не возникает никаких проблем.
•Затем лишь остается проверить значение свойства MessageClass, которое имеется у всех объектов, представляющих элементы Outlook. У объектов, представляющих сообщения электронной почты, это свойство равно IPM.Note, а у объектов извещений —
Report.IPM.Note.IPNRN.
•Простая проверка позволяет убедиться в том, что ссылку, которая хранится в переменной FolderItem можно присвоить объекту CurMsg. Если же среди элементов коллекции будет обнаружен объект класса ReportItem, его обработка выполняться не будет, а цикл перейдет к следующему элементу.
•Затем начинается обработка сообщения. Здесь снова используется функция DateDiff, с помощью которой определяется, попадает ли очередное сообщение в заданный пользователем интервал.
254 Глава 9. Программирование в Outlook: документооборот и электронная почта
•Если сообщение соответствует этому условию, процедура получает значения его свойств, выводя их в столбцах и строках листа Excel (обратите внимание на использование функции
GoNext).
•В том случае, когда сообщение содержит вложения (т.е. свойство CurMsg.Attachments.Count имеет ненулевое значение), выполняется последовательный перебор всех элементов коллекции CurMsg.Attachments. Так как количество вложений и названия файлов могут иметь разную длину, процедура пытается отформатировать текущую ячейку таким образом, чтобы в ней поместилась вся соответствующая информация и чтобы ячейка при этом была достаточно компактной.
•При выводе даты получения или отправки сообщения применяется формат, обеспечивающий единообразное представление значения времени для последующей сортировки сообщений.
•Наконец, так как данная процедура универсальна и применяется для выборки как полученных, так и отправленных сообщений, в ней имеются операторы, которые немного изменяют ее алгоритм в соответствии с тем, какой тип папки обрабатывается, с целью упрощения последующего форматирования и улучшения внешнего вида отчета.
Рис. 9.19. Отчет о сообщениях, полученных и отправленных в период Рождественских каникул 2001 года (значительная часть отчета выходит за пределы экрана)
Подготовив текст процедуры, сохраните проект и попробуйте выполнить тестовый “прогон приложения”. Если не было допущено ошибок, после успешного завершения работы макроса появится результат, подобный приведенному на рис. 9.19.
На этом создание основной части приложения можно считать завершенным. Удалите только что созданный лист и сохраните проект.
Доводка приложения Registration до профессионального уровня
Конечно же, хотя в настоящее время уже можно получить полноценный отчет, работать с ним все же сложно, так как он не отформатирован. Нам осталось разработать лишь несколько вспомо-
Создание приложения Registrator 255
гательных процедур, которые облегчат восприятие отчета и обеспечат его профессиональный вид. Здесь возникает две проблемы — во-первых, количество строк в отчете, как по входящим сообщениям, так и по исходящим меняется от нуля до… очень больших значений. Следовательно, эти процедуры должны корректно определить, имеются ли входящие или исходящие сообщения вообще и, если это так, какой диапазон они занимают. Вторая проблема состоит в том, что отдельные элементы сообщений, такие как тема, перечень вложений или получателей копий письма, могут иметь очень большую длину, что нужно учесть при форматировании.
Ниже приведены исходные тексты оставшихся процедур, которые позволяют решить указанные выше проблемы и получить полнофункциональное приложение, практически профессионального вида. Так как эти процедуры носят исключительно вспомогательный характер, изучив их исходный текст, можно привести хорошо известное высказывание: “О вкусах не спорят”. Данные процедуры приводятся только для справки, чтобы облегчить изучение излагаемого материала. Так как разработанное приложение имеет ярко выраженную модульную структуру можно, при желании, заменить эти процедуры другими.
В листинге 9.16 приведены недостающие процедуры модуля main, обеспечивающие вычисление диапазонов ячеек, в которых содержатся сведения о зарегистрированных входящих и исходящих сообщениях.
ЛИСТИНГ 9.16
Sub CalculateInputRange()
OutputDataRange = ActiveCell.Address
If OutputDataRange <> "$" & sFirstCol & "$4" Then LastInputRow = LTrim(Str(Val(Mid(OutputDataRange, 4, _
Len(OutputDataRange) - 3)) - 2))
InputDataRange = "$" & sFirstCol & "$3:$" & sLastCol & _ "$" & LastInputRow
Else
'*** Нет ни одного входящего сообщения
LastInputRow = "3"
GoNext (misc.sStart): GoNext (sRow): GoNext (sRow) ActiveCell = "За указанный период сообщений не было"
InputDataRange = ActiveCell.Address & ":" GoNext (sRow)
GoNext (sRow) End If
End Sub
Sub CalculateOutputRange()
OutputDataRange = ActiveCell.Address LastInputRow = "$" & sFirstCol & "$" & _
LTrim(Str(Val(LastInputRow) + 4)) If OutputDataRange <> LastInputRow Then
OutputDataRange = LastInputRow & ":$" & sLastCol & "$" & _
LTrim(Str(Val(Mid(OutputDataRange, 4, _ Len(OutputDataRange) - 3)) - 1))
Else
'*** Нет ни одного исходящего сообщения
OutputDataRange = ActiveCell.Address & ":" ActiveCell = "За указанный период сообщений не было"
End If
End Sub
В листинге 9.17 приведены процедуры модуля misc, обеспечивающие создание заголовков отчетов, а также окончательное форматирование сформированных отчетов.
256 Глава 9. Программирование в Outlook: документооборот и электронная почта
Рис. 9.20. Отформатированный отчет воспринимается гораздо лучше
ЛИСТИНГ 9.17
Sub MakeReportHeader(sFolderType As String, _
ByVal sHeaderText As String)
Dim sRange, sHeaderRange As String ActiveCell = sHeaderText sRange = ActiveCell.Address
sRange = sRange & sSemicolon & sDollar & sLastCol & _ sDollar & Mid(sRange, 4, Len(sRange) - 3)
sHeaderRange = sRange Range(sRange).Select With Selection
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlBottom
.WrapText = False
.Orientation = 0
.AddIndent = False
.ShrinkToFit = False
.MergeCells = False End With Selection.Merge
Selection.Font.Bold = True
MakeBorder (iTopHeader)
GoNext (sHome): GoNext (sRow)
If sFolderType = sInbox Then
Создание приложения Registrator 257
ActiveCell = "От кого"
Else
ActiveCell = "Кому"
End If: sRange = ActiveCell.Address: GoNext (sCol)
ActiveCell = "Размер": GoNext (sCol)
ActiveCell = "Вложения": GoNext (sCol) ActiveCell = "Дата и время": GoNext (sCol)
ActiveCell = "Тема": GoNext (sCol) ActiveCell = "Учетная запись": GoNext (sCol)
sRange = sRange & sSemicolon & sDollar & sLastCol & _ sDollar & Mid(sRange, 4, Len(sRange) - 3)
Range(sRange).Select MakeGrid (IsOneRow(sRange)) MakeBorder (iBottomHeader) Selection.Font.Size = 8 Selection.Font.Bold = True
Selection.HorizontalAlignment = xlCenter GoNext (sHome)
GoNext (sRow)
End Sub
Sub FormatReport(Selector As String)
If Right(Selector, 1) = sSemicolon Then
Selector = Selector & sDollar & sLastCol & sDollar & _
Mid(Selector, 4, Len(Selector) - 4)
Range(Selector).Select
Selection.Font.Bold = True
Selection.Font.Italic = True
MakeBorder
Else
Range(Selector).Select
Selection.RowHeight = iGoodHeight
MakeGrid (IsOneRow(Selector))
MakeBorder
Selection.Columns.AutoFit
Selection.Sort Key1:=Range(sTimeKey), Order1:=xlAscending, _
Header:=xlNo, OrderCustom:=1, _
MatchCase:=False,Orientation:=xlTopToBottom
Range(sAddressCol).Select
Selection.ColumnWidth = iGoodWidth
Range(sSubjectCol).Select
Selection.ColumnWidth = iGoodWidth
Range(sAttachmentsCol).Select
If MaxAttachmentsLen > iGoodAttachWidth Then _
MaxAttachmentsLen = iGoodAttachWidth
Selection.ColumnWidth = MaxAttachmentsLen / 2
GoNext (sStart)
End If
End Sub
Sub MakeGrid(JustOneRow As Boolean)
Selection.Borders(xlEdgeLeft).LineStyle = xlContinuous Selection.Borders(xlEdgeTop).LineStyle = xlContinuous Selection.Borders(xlEdgeBottom).LineStyle = xlContinuous Selection.Borders(xlEdgeRight).LineStyle = xlContinuous
258 Глава 9. Программирование в Outlook: документооборот и электронная почта
Selection.Borders(xlInsideVertical).LineStyle = xlDouble If Not JustOneRow Then _
Selection.Borders(xlInsideHorizontal).LineStyle = _ xlContinuous
End Sub
Sub MakeBorder(Optional Control As Integer = 0)
With Selection.Borders(xlEdgeLeft)
.LineStyle = xlContinuous
.Weight = xlMedium
End With
With Selection.Borders(xlEdgeTop)
.LineStyle = xlContinuous
If Control = iBottomHeader Then
.Weight = xlThin
Else
.Weight = xlMedium
End If
End With
With Selection.Borders(xlEdgeBottom)
.LineStyle = xlContinuous
If Control = iTopHeader Then
.Weight = xlThin
Else
.Weight = xlMedium
End If
End With
With Selection.Borders(xlEdgeRight)
.LineStyle = xlContinuous
.Weight = xlMedium
End With
End Sub
Function IsOneRow(ByVal sRange As String) As Boolean
Dim SemicolonPosition As Integer
IsOneRow = False
SemicolonPosition = InStr(sRange, ":")
If Len(Left(sRange, SemicolonPosition - 1)) = _
Len(Right(sRange, Len(sRange) - SemicolonPosition)) Then
IsOneRow = StrComp(Mid(sRange, 4, SemicolonPosition - 4), _
Mid(sRange, SemicolonPosition + 4, _
Len(sRange) - SemicolonPosition - 3)) = 0
End If
End Function
Sub FormatHeaderCell()
With Selection
.Borders(xlEdgeLeft).LineStyle = xlContinuous
.Borders(xlEdgeTop).LineStyle = xlContinuous
.Borders(xlEdgeBottom).LineStyle = xlContinuous
.Borders(xlEdgeRight).LineStyle = xlContinuous
.Font.Size = 8
.Font.Bold = True
.HorizontalAlignment = xlCenter End With
Создание приложения Registrator 259
End Sub
Создав указанные процедуры, сохраните проект и запустите приложение. Теперь получаемые с помощью приложения Registrator результаты будут соответствовать тем, которые представлены на рис. 9.20 и 9.21.
Рис. 9.21. Ситуация с отсутствующими сообщениями обрабатывается корректно
Доводка и совершенствование приложения
Итак, специалисты ООО “Универсал” с честью справились с возложенной на них задачей — в результате долгого и кропотливого труда было создано надежное и устойчиво работающее приложение, обеспечивающее автоматическую регистрацию всех входящих и исходящих сообщений.
Естественно, руководству очень скоро оказалось этого мало: “А можно ли выводить отчет по определенной учетной записи? А по всем учетным записям? А если нужно получить сведения об объемах пересылаемой почты по месяцам или по кварталам? А нельзя ли создать отчет в цвете для распечатки на цветном принтере? Как насчет идеи сохранения всей регистрационной информации в базе данных Access 2000?…”
Да, действительно, данное приложение дает возможность использовать его в качестве исходной платформы, совершенствуя и развивая его в нужном направлении. Остается лишь пожелать успехов в этом нелегкой, но интересной работе.
Приложение
VBA, как язык программирования: данные, синтаксис и функции
Как язык программирования, VBA во всем похож на Visual Basic (об этом говорит уже само название VBA — Visual Basic для приложений). Не следует путать VBA c Visual Basic, поскольку это физически разные вещи — Visual Basic представляет собой самостоятельный пакет для разработки программного обеспечения, не имеющий прямого отношения к пакету MS Office. Программы на языке Visual Basic — это самостоятельные исполняемые файлы, такие же, как MS Word или MS Excel, в то время как VBA-программы “живут” только в рабочей среде документов Office, поскольку VBA — это одно из встроенных инструментальных средств приложений Office.
Однако, с точки зрения программиста — в синтаксическом, и во многих других отношениях — Visual Basic и VBA (Visual Basic для приложений), это одно и то же. Более того, у VB и VBA есть еще третий “брат-близнец”: язык описания Web-сценариев VBScript, который также совместим на уровне синтаксиса с VBA и VB. В этом заключается великая сила и огромное преимущество технологии Visual Basic, которая использует один универсальный язык для решения столь различных задач.
И в этом же заключается главная причина, по которой любому человеку, имеющему дело с компьютерами, стоит хоть немного владеть синтаксисом Visual Basic — никогда не знаешь, где он сможет тебе пригодиться. Язык программирования Visual Basic позволяет заниматься как офисным программированием на VBA, так и созданием Windows-приложений при помощи пакета Visual Basic, а еще можно создавать Web-сценарии на языке VBScript. “Три в одном” — это ли не причина для того, чтобы хоть немного освоить азы языка программирования Visual Basic (особенно, учитывая тот факт, что многие приложения сторонних производителей в наше время снабжаются встроенными языками, очень похожими на Visual Basic)? Как знать, не придется ли нам всем в недалеком будущем программировать новый холодильник или стиральную машину именно на
языке Visual Basic?
Данные VBA: типы данных, переменные и константы
Изучение любого языка программирования начинают с данных, которыми оперируют программы на этом языке. В самом деле, невозможно понять, как выполнить ту или иную операцию над каким-нибудь объектом, не понимая, что этот объект собой представляет.
В случае Visual Basic дело обстоит и проще, и в то же время сложнее. Никакой “серьезный” язык программирования не позволит использовать переменные, которые не были предварительно объявлены. В Visual Basic разрешено использовать данные без явного или неявного указания их типа. Можно вообще не задумываться о данных, придумывая переменные “на ходу” и тут же используя их в своем коде, как заблагорассудится, объявляя переменную без указания ее типа и используя ее для хранения данных практически любого типа.
Однако за все надо платить, и платой за такую свободу будет низкая эффективность программного кода, а также большое количество ошибок. Как показывает практика, только небольшие и несложные программы можно создавать подобным способом.
Но Visual Basic гибок и универсален, и если вы хотите работать серьезно, и создавать скольконибудь сложные программы, то у вас есть возможность обращаться с данными так, как это делают профессиональные программисты.
Данные VBA: типы данных, переменные и константы 263
Переменные и константы
Для хранения данных используют переменные и константы. Разница между ними заключается в том, что переменная может менять свое значение в процессе выполнения программы, а константа — это просто какое-то значение, которому для удобства обращения с ним присвоено имя.
Константы
Константы применяют в случаях, когда требуется много раз использовать в программе одно и то же значение. Тогда для того, чтобы изменить это значение, достаточно будет изменить только текст объявления константы. Кроме этого, константы делают текст программы более “прозрачным” и легким для понимания. Например, при вызове многих функций используются предопределенные константы Visual Basic, например:
If MsgBox("Создать письмо?", vbYesNo, "Отрицательное сальдо") = _ vbYes ...
...
Здесь константы vbYes и vbYesNo — это просто числа, запоминать которые нет нужды, поскольку имена констант запомнить гораздо легче. Константа vbYesNo означает, что в окне сообщения должны присутствовать кнопки Yes (Äà) и No (Íåò), а константа vbYes означает, что пользователь выбрал кнопку Yes(Äà). Существует огромное число таких предопределенных констант, при помощи которых задают параметры для функций, свойств и методов объектов, а также используют во многих других случаях.
Объявление констант
Программист может сам определить константы для собственных нужд. Например, можно определить константу для процентной ставки, используемой в вычислениях:
Public Const Stavka1 As Single = 0.16667
Здесь Stavka1 — это имя константы: далее в программе можно использовать это имя везде, где требуется значение 0.16667. Зарезервированное слово Public означает, что константа будет “видна” во всех модулях и во всех процедурах. Такие данные должны объявляться на уровне модуля, то есть вне текста какой-либо процедуры. Тип числового значения здесь определен, как Single (о типах см. далее). Если вместо Public использовать слово Private, то константу будут “понимать” только те процедуры, которые находятся только в этом же модуле.
Далее можно объявить еще одну константу, используя при этом уже объявленное имя
Stavka1:
Public Const Stavka2 As Single = Stavka1 + 0.007
Иопять надо будет использовать имя Stavka2 везде, где требуется значение 0.16667 + 0.007.
Врезультате можно будет, изменив всего одну строку кода, поменять значения всех ставок, которые участвуют в вычислениях.
Константы, которые используются локально, то есть только в той процедуре, где они объявле-
ны, задаются без слов Public или Private, их объявление помещается внутри текста процедуры. Например, можно определить константами текст сообщения об ошибке и максимальное число ошибок:
Const ErrorMessage1 As String = "Произошла ошибка 1"
...
Const MaxErrors As Integer = 100
264 Приложение. VBA, как язык программирования: данные, синтаксис и функции
Переменные
Переменные, в противоположность константам, могут менять свое значение в процессе выполнения программы. Но, как и к константам, обращение к переменным производится по их именам. Переменная может принадлежать к одному из простых типов или же к типу, определенному пользователем, как комбинация простых типов. Кроме этого, переменная может быть объявлена, как массив.
Объявление переменных
Рассмотрим следующие примеры объявления переменных:
Dim WordRunning As Boolean
Логическая (булева) переменная WordRunning может принимать только значения True (Истина) или False (Ложь).
Dim WordCount, WordPointer As Integer
Целочисленным переменным WordCount и WordPointer отныне разрешено присваивать в качестве значений только целые числа.
А вот хорошо знакомые примеры объявления объектных переменных:
Dim MyParagraph As Paragraph
Dim MyDocument As Document
Если тип переменной явно не указан, по умолчанию будет принят тип Variant (о типе Variant и вообще о типах см. далее).
Инициализация переменных
Автоматически, в момент запуска программы, переменные численных типов инициализируются значением 0, а переменные строковых типов — значением “пустая строка”. Переменные типа Variant инициализируются значением Empty, а переменные объектных типов — значением
Nothing.
Объявление переменных с областью видимости на уровне модуля необходимо помещать в начало модуля, вне текста какой-либо процедуры. Объявление переменных внутри процедуры следует помещать в начало процедуры (это повышает “прозрачность” текста и облегчает его понимание). Везде, где это возможно, желательно использовать локальные переменные на уровне процедуры — неумеренное использование переменных, объявленных на уровне модуля, усложняет текст и затрудняет его понимание.
Зачем объявлять переменные?
Вообще говоря, переменные можно и не объявлять — Visual Basic может, при желании, допускать такую свободу в обращении со своими данными. Можно просто использовать любое имя в левой части оператора присваивания:
SvodCount = 2
И затем, внутри цикла For, использовать и модифицировать значение получившейся перемен-
ной SvodCount:
For A = 1 To PriceA.Range("Наименование").Rows.Count
...
SvodCount = SvodCount + 1
...
Next A