Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© Андрей Колесов, Ольга Павлова, 2001Оператор DoEvents позволяет выполнять параллельные процессы, поэтому достаточно часто используется для синхронизации двух различных вычислительных процессов.
Типичным случаем является такой пример. Имеются две формы: главная (frmMain) выполняет некоторые вычисления и выводит результаты, а вторая (frmEntry) вводит исходные данные для этих вычислений. При этом логика взаимодействия данных форм такова: главная форма запускает frmEntry и ожидает, когда там будут введены нужные данные, например, в виде нажатия пользователем кнопки Submit (Подтверждение).
Один из вариантов решения этой задачи может выглядеть следующим образом:
Private Sub Command1_Click() ' процедура в форме frmMain Dim Myform As frmEntry ' создание второй формы для ввода данных Set Myform = New frmEntry With Myform .Show .Ready = False ' начальная установка глобальной переменной Do ' ожидание DoEvents ' передача управления операционной системе ' для обработки других событий Loop Until .Ready 'Выполнение каких-то вычислений на основе введенных данных txtResults.Text = .txtNum1 * .txtNum2 End With Unload Myform Set Myform = Nothing ' освободить объект End Sub ' '============ ' код формы frmEntry1 Public Ready As Boolean Private Sub cmdSubmit_Click() Ready = True ' подтверждение ввода End Sub
Данная конструкция базируется на отслеживании состояния глобальной переменной Ready в форме frmEntry1. Однако недостатком этой конструкции является как раз использование оператора DoEvents, который требует достаточно много времени, то есть "съедает" значительную часть ресурсов.
Гораздо лучше выглядит такой вариант решения, когда создается пользовательское событие, управляемое из формы frmEntry. Для этого в данной форме нужно написать следующий код:
' описание события в секции Declaration Public Event NumbersSubmitted(Num1 As Integer, Num2 As Integer) ' выполнение операций Private Sub cmdSubmit2_Click() Dim Num1%, Num2% Num1 = CInt(txtNum1.Text) Num2 = CInt(txtNum2.Text) Unload Me ' запуск внешнего события с передачей параметров RaiseEvent NumbersSubmitted(Num1, Num2) End Sub Соответственно в главной форме нужно описать данное событие и сформировать процедуру его обработки: Private WithEvents frmNumEntry As frmEntry Private Sub Command2_Click() ' запуск второй формы Set frmNumEntry = New frmEntry frmNumEntry.Show End Sub Private Sub frmNumEntry_NumbersSubmitted(Num1%, Num2%) ' обработка события, инициализированного из формы frmEntry txtResults.Text = Num1 * Num2 Set frmNumEntry = Nothing End Sub
Мы неоднократно подчеркивали в своих публикациях, что причиной достаточно частых ошибок является использование неявного преобразования типов данных (см. например, Совет 353). Подобная возможность (без применения специальных функций преобразования) является большим недостатком VB, а ее использование разработчиками говорит в первую очередь об их небольшом опыте...
Здесь существует достаточно много подводных камней, главный из которых — неопределенность такого рода преобразования, то есть тот факт, что программа будет вести себя совсем не так, как видится ее автору. Следует также учитывать особенности национальных форматов представления данных (для вещественных чисел и дат). Приведем еще один пример на данную тему, реализованный в Windows с русскими региональными установками.
Выполните такой код:
Dim strSource As String, strR1 As String, strR2 As String Dim sngResult As Single strSource = "2.34" ' преобразование строки в вещественное число sngResult = Val(strSource) Print sngResult ' будет напечатано 2,34 ' strR1 = Str(sngResult) strR2 = sngResult Print strR1, strR2 ' будет напечатано 2.34 2,34
Если вы попробуете выполнить код:
strSource = "2.34" sngResult = strSource
то получите на втором операторе сообщение об ошибке — неверный тип данных. Далее выполните еще один код:
strSource = "2,34" sngResult = Val(strSource) Print sngResult ' будет напечатано 2 ! Ошибка sngResult = Val(strSource) strR1 = Str(sngResult) strR2 = sngResult Print strR1, strR2 ' будет напечатано 2.34 2,34
Из проведенных экспериментов можно сделать следующие выводы:
Допустим, вы хотите собрать список папок в некий список. Казалось бы, узнать имена папок можно следующим образом:
Dim AllFolders As Folders Set AllFolders = Application.GetNamespace("MAPI").Folders MsgBox AllFolders.Count For i = 1 To AllFolders.Count MsgBox AllFolder.Item(i).Name Next
Однако выясняется, что у вас имеется всего две папки с именами Personal Folders. Здесь полезно вспомнить, что папки OutLook имеют иерархическую структуру, такую же, как знакомая файловая система. В частности, она может иметь вид, представленный на рис.362-1. Приведенная выше конструкция выдала нам имена стандартных папок самого верхнего уровня.
Рис. 362-1
Чтобы получить информацию о папках второго уровня, нужно написать более сложный код:
Dim allFolders As Folders Dim i%, j% Set allFolders = Application.GetNamespace("MAPI").Folders MsgBox "Число папок верхнего уровня = " & allFolders.Count ' обзор папок верхнего уровня For i = 1 To allFolders.Count MsgBox "Имя папки = " & _ allFolders.Item(i).Name & vbCrLf & _ " число вложенных папок = " & _ allFolders.Item(i).Folders.Count ' обзор папок второго уровня For j = 1 To allFolders.Item(i).Folders.Count MsgBox "Имена вложенной папки = " & _ allFolders.Item(i).Folders.Item(j).Name & vbCrLf & _ " число вложенных в нее папок = " & _ allFolders.Item(i).Folders.Item(j).Folders.Count Next Next
Однако понятно, что наращивание числа вложенных циклов для обзора иерархических структур является совершенно бесперспективным занятием. (В нашем примере одна из папок второго уровня — Contacts — имеет также вложенную папку.) Здесь требуется переходить к рекурсивным конструкциям, которые могут выглядеть примерно так:
Dim allFolders As Folders Dim intLevel% ' номер уровня intLevel = 0 Set allFolders = Application.GetNamespace("MAPI").Folders Call FoldersViewRecurse(allFolders, intLevel, "MAPI") Sub FoldersViewRecurse(allFolders As Folders, intLevel%, strName$) Dim i%, FolderName$ Dim newFolders As Folders ' Вывод информации о папках данного узла иерархической структуры Debug.Print "Уровень = "; intLevel; " Узел = "; _ strName$; Tab(45); " Вложенных папок = "; allFolders.Count If allFolders.Count > 0 Then ' есть вложенные папки For i = 1 To allFolders.Count ' обзор вложенных папок FolderName$ = allFolders.Item(i).Name Set newFolders = allFolders.Item(i).Folders ' рекурсивное обращение к самой себе: Call FoldersViewRecurse(newFolders, intLevel + 1, FolderName$) Next End If End Sub
В правильности работы данной конструкции легко убедиться, взглянув на полученную распечатку результатов (рис. 362-2).
Рис. 362-2
Это производится приблизительно следующим образом:
Dim myNewContact As ContactItem ' создание объекта "Контакт" Set myNewContact = Application.CreateItem(olContactItem) ' далее заполняются нужные поля формы myNewContact.FirstName = "Андрей" myNewContact.LastName = "Колесов" myNewContact.Email1Address = "akolesov@online.ru" myNewContact.Close olSave ' сохранить
Можно также выдать диалоговое окно "Контакты" для заполнения пользователем:
myNewContact.Display
Однако данная конструкция записывает новый контакт в стандартную папку "Контакты". Если вам нужно работать с какой-то индивидуальной папкой, вы должны написать такой код (здесь мы вдобавок создаем новую папку):
Dim myNewContact As ContactItem Dim myNewFolder As MAPIFolder ' Создание папки типа "Контакты" Set myNewFolder = Application.GetNamespace("MAPI"). _ GetDefaultFolder(olFolderContacts).Folders.Add("Личная") ' создание объекта "Контакт" для данной папки Set myNewContact = myNewFolder.Items.Add(olContactItem) myNewContact.FirstName = "Андрей" ...
Вам бы хотелось автоматически обрабатывать входящие письма? Это довольно просто сделать с помощью такого кода:
Private Sub Application_NewMail() ' При поступлении нового письма ' производится его обработка Dim mailItems As Items Dim mailmsg As MailItem ' Набор писем из папки "Входящие" Set mailItems = Application.Session._ GetDefaultFolder(olFolderInbox).Items Set mailmsg = mailItems.GetLast ' выбираем последнее ' далее выполняется анализ письма ' (его реквизитов, содержимого и пр. ' ... ' по результатам анализа можно: mailmsg.UnRead = False ' установить признак "Прочтенное" mailmsg.Delete ' удалить mailmsg.Move(myFolder) ' переместить в другую папку End Sub
Это можно сделать, например, с помощью следующей простой макрокоманды, которая создает новое письмо, автоматически заполняет его содержимое и далее предоставляет пользователю возможность вводить остальную информацию и отправлять письмо:
Sub NewMailToKolesov() ' создание нового письма Dim myMail As MailItem Set myMail = CreateItem(olMailItem) ' заполнение его полей myMail.To = "akolesov@online.ru" myMail.Subject = "Привет!" myMail.body = "Андрей!" & vbCrLf & _ "Я тут придумал такую классную штуку." myMail.Display ' выводим окно и дополняем текст End Sub
Но возможен и другой вариант — вы уже создали новое письмо и хотите в процессе его ввода сделать вставку какого-то текста. В этом случае вам пригодится макрокоманда такого вида:
Sub InsertText() 'Вставить текст в текущее окно Dim myMail As MailItem ' выбирает текущее окно (т.е. нового письма) Set myMail = Application.ActiveInspector.CurrentItem myMail.body = myMail.body + " Привет семье!" End Sub