Visual2000 · Архив статей А.Колесова & О.Павловой

Советы тем, кто программирует на VB & VBA

Андрей Колесов, Ольга Павлова

© Андрей Колесов, Ольга Павлова, 2001
Авторский вариант. Статья была опубликована c незначительной литературной правкой в журнале "КомпьютерПресс" N 03/2001, CD-ROM.


Совет 360. Вместо DoEvents отслеживайте реальные события

Оператор 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  

В начало статьи

Совет 361. Избегайте неявного преобразования типов данных

Мы неоднократно подчеркивали в своих публикациях, что причиной достаточно частых ошибок является использование неявного преобразования типов данных (см. например, Совет 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  

Из проведенных экспериментов можно сделать следующие выводы:

  1. Функции явного и неявного преобразования данных работают по-разному! Val и Str выполняют операции преобразования по правилам американских региональных установок, независимо от установок пользователя на его компьютере. Неявное преобразование выполняется с учетом установленных на компьютере параметров.
  2. Казалось бы, оба варианта имеют свои недостатки. Более того, на "русской" системе лучше использовать неявное преобразование. Но здесь стоит обратить внимание на следующий момент: функции Val и Str будут одинаково работать на любом ПК, тогда как операции неявного преобразования могут выдавать разные результаты в зависимости от региональных установок.

В начало статьи

Совет 362. Как узнать список папок Outlook

Допустим, вы хотите собрать список папок в некий список. Казалось бы, узнать имена папок можно следующим образом:

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

В начало статьи

Совет 363. Как добавить новый контакт в папку Outlook

Это производится приблизительно следующим образом:

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 = "Андрей"  
...  

В начало статьи

Совет 364. Как обрабатывать входящие письма

Вам бы хотелось автоматически обрабатывать входящие письма? Это довольно просто сделать с помощью такого кода:

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  

В начало статьи

Совет 365. Как вставить текст в создаваемое письмо

Это можно сделать, например, с помощью следующей простой макрокоманды, которая создает новое письмо, автоматически заполняет его содержимое и далее предоставляет пользователю возможность вводить остальную информацию и отправлять письмо:

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  

В начало статьи