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

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

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

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


Совет 366. Как узнать адрес отправителя письма в Outlook 2000

Нам не удалось найти универсальный ответ на этот вопрос. Если, например, пришло новое письмо и мы хотим узнать адрес отправителя, можно написать следующий код в процедуре Application_NewMail():

 ' При поступлении нового письма  
 ' производится его обработка  
 Dim mailItems As Items  
 Dim mailmsg As MailItem  
 Dim Sender$, SenderEmail$  
   
 ' Набор писем из папки "Входящие"  
 Set mailItems = Application.Session._  
    GetDefaultFolder(olFolderInbox).Items  
 Set mailmsg = mailItems.GetLast ' выбираем последнее  
 Sender$ = mailmsg.SenderName  

В этом случае мы прочитали имя отправителя (в строке From/Откуда). Но как узнать его электронный адрес? К сожалению, подходящего для этой цели свойства мы у объекта MailItem не обнаружили.

А вот если данный отправитель уже внесен в вашу адресную книгу, вы можете узнать его координаты. Это делается следующим образом:

 Dim repct As Recipient  'описание контакта в книге 
 ' создание объекта с именем отправителя  
 Set repct = itm.Recipients.Add (mailmsg.SenderName)  
 recpt.Resolve    'проверка — есть ли какой контакт в книге?  
 If recpt.Resolved Then  ' есть контакт  
   SenderEmail$ = recpt.AddressEntry.address  ' адрес E-mail!  
 End If  

Понятно, что имена отправителей в письме и в адресной книге должны быть совершенно идентичны.

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

Совет 367. Поиск файлов с помощью объекта FileSearch

Задача поиска файлов по некоторым критериям встречается довольно часто. Например, вам нужно найти самый последний измененный файл в некотором каталоге. Для этого можно написать достаточно простой код с использованием функции Dir, с помощью которой делается выборка всех файлов по заданному шаблону:

' поиск самого последнего модифицированного файла
Dim FileName$, LastFile$, ThisDate As Date, LastDate As Date
Dim PathName$, Template$

PathName = "c:\" ' поиск в корневом каталоге C:
Template = PathName & "*.*" ' все файлы
FileName = Dir(Template) ' инициализация
LastDate = #1/1/80#
' просмотр файлов в заданном каталоге
Do While FileName <> ""
  ThisDate = FileDateTime(PathName & FileName) ' дата и время
  ' поиск макс. даты (последней)
  If ThisDate > LastDate Then ' нашли более поздний
    LastDate = ThisDate
    LastFile = PathName & FileName
  End If
  FileName = Dir ' выборка следующего
Loop
If LastFile <> "" Then 'что-то найдено
  MsgBox "Последний по дате файл по шаблону " & _
         Template & vbCrLf & _
         "Имя файла = " & LastFile & vbCrLf & _
         "Дата коррекции = " & LastDate
Else
  MsgBox "Вообще нет файлов с шаблоном " & Template
End If

Однако если помимо этого потребуется поиск в подкаталогах, то придется дополнительно сделать их выборку, используя рекурсивные конструкции. В принципе, это не очень сложно (см. совет 230), но все же требует дополнительных усилий и некоторого опыта программирования. Тогда как в среде Office/VBA проблема может быть решена гораздо проще — путем использования объекта FindSearch. Так, например, следующий код позволяет найти все файлы, содержащиеся в каталоге D:\TMP и во всех вложенных подкаталогах:

With Application.FileSearch 
  .LookIn = "d:\tmp"  
  .SearchSubFolders = True  
  .FileType = msoFileTypeAllFiles  
  If .Execute  > 0 Then  
    MsgBox "Число найденных файлов = " & .FoundFiles.Count  
    ' вывод имен файлов  
    For i = 1 To .FoundFiles.Count  
      MsgBox .FoundFiles(i)  
    Next i  
  Else  
    MsgBox "Не найдено подходящих файлов"  
  End If  
End With  

Здесь существуют богатые возможности управления режимами выборки, в том числе с поиском по контексту, датам последней модификации и пр. Очень удобно, что имена найденных файлов (свойство FoundFiles) выдаются в виде полных имен. Очевидно, что после получения списка файлов можно выполнить более "тонкую" выборку, например с более жесткими ограничениями по интервалам даты или размера.

Все это можно использовать в обычном VB (или в других системах программирования, поддерживающих ActiveX), подключив библиотеку Microsoft Word 8.0/9.0 Object Library.

Более того, метод Execute позволяет получить список файлов, отсортированный по реквизитам файлов: именам, типам, дате последней модификации и размеру. Так что найти последний измененный файл можно очень просто:

With Application.FileSearch
  .LookIn = "d:\tmp"  
  .SearchSubFolders = True  
  .FileType = msoFileTypeAllFiles  
  If .Execute(SortBy:=msoSortByLastModified, _  
      SortOrder:=msoSortOrderDescending) > 0 Then  
    MsgBox "Последний измененный файл = " & .FoundFiles(i)  
  End If  
End With  

Однако тестирование показало, что в Word 2000 сортировка именно по дате модификации почему-то не работает. Но дальнейшие исследования выявили, что она начинает работать (!) после выполнения поиска с сортировкой по размеру файлов. Такой вариант работает корректно:

With Application.FileSearch
  .LookIn = "d:\tmp"  
  .SearchSubFolders = True  
  .FileType = msoFileTypeAllFiles  
  .Execute(SortBy:=msoSortBySize)  ' фиктивная выборка, чтобы  
                                   ' работала следующая строка кода  
  If .Execute(SortBy:=msoSortByLastModified, _  
      SortOrder:=msoSortOrderDescending) > 0 Then  
    MsgBox "Последний измененный файл = " & .FoundFiles(i)  
  End If  
End With  

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

Совет 368. Сортировка содержимого ListView

Во многих программах, например в Outlook и Windows Explorer, можно выполнять сортировку содержимого элемента управления ListView с помощью щелчка мышью по заголовку колонки. При этом порядок сортировки меняется между вариантами "по возрастанию" и "по уменьшению". Чтобы добавить подобную функциональность в свой проект, создайте в стандартном модуле следующую процедуру:

Public Sub SortListView(ByVal lvw As MSComctlLib.ListView, _
   ByVal colHdr As MSComctlLib.ColumnHeader)  
   
  ' установка режима сортировки для указанной колонки  
  lvw.SortKey = colHdr.Index - 1  
  lvw.Sorted = True  
   
  ' изменение сортировки меняется между  
  ' "по возрастанию" и "по уменьшению"  
  lvw.SortOrder = 1 Xor lvw.SortOrder  
End Sub  

Чтобы обеспечить выполнение данной операции при щелчке мышью на заголовках, используйте событие ColumnClick для конкретного элемента управления:

Private Sub lvwMyListView_ColumnClick(ByVal ColumnHeader As _ 
  MSComctlLib.ColumnHeader)  
  SortListView lvwMyListView, ColumnHeader  
End Sub  

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

Совет 369. Как определить имя дисковода CD-ROM с помощью FileSystemObject

Как известно, библиотека Scrrun.dll содержит объект FileSystemObject, позволяющий выполнять массу полезных операций с файловой системой. Библиотека входит в состав всех последних модификаций Windows (начиная с обновленного варианта Windows 95) и подключается к проекту посредством команды References; имя библиотеки в списке — Microsoft Scripting Runtime.

С помощью объекта FileSystemObject легко определяется имя дисковода CD-ROM:

Dim CDPath as String
Dim fso As New Scripting.FileSystemObject
Dim drv As Drive

For Each drv In fso.Drives  ' перебор всех устройств
  If drv.DriveType = CDRom Then  ' нашли
    CDPath = drv.Path
    Exit For
  End If
Next drv
Set drv = Nothing
Set fso = Nothing

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

Совет 370. Как передать текст из Rich Textbox в Microsoft Word

Задача обработки форматированного текста в VB-приложениях довольно часто решается посредством элемента управления Rich Textbox. В то же время для обработки текстов полезно бывает использовать функции Word (например, проверку грамматики). Соответственно возникает необходимость обмена данными между Rich Textbox и Word.

Это можно сделать, например, с помощью ввода-вывода RTF-файла, но гораздо проще передать информацию через буфер обмена с помощью объекта Clipboard, а затем, используя механизм OLE Automation, открыть приложение Word и вставить в пустой документ отформатированный текст.

Следующая процедура показывает, как выполнить эту операцию (нужно только установить ссылку на библиотеку Microsoft Word 8.0/9.0 Object):

Dim wrdApp As Word.Application
   
Private Sub Form_Load()  
  Set wrdApp = New Word.Application  
End Sub  

Private Sub Command2_Click()  
  '  
  ' записать текст из Rich Textbox в буфер обмена  
  Clipboard.SetText RichTextBox1.TextRTF, vbCFRTF  
   
 ' записать в текст в Word  
  With wrdApp  
    .Documents.Add    ' новый документ  
    .Selection.Paste  ' вставить  
      ' запомнить файл  
    .ActiveDocument.SaveAs App.Path & "\RTFDOC2.doc", wdFormatDocument  
    .Visible = True  
    .Activate  ' сделать документ активным и видимым  
  End With  
End Sub  

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

Совет 371. Как установить VBA SDK

Как известно, для того чтобы обеспечить возможность совместной работы набора VBA SDK (сейчас доступна версия 6.2) со средой VB 6.0, для последнего должен быть установлен Service Pack 3 (подробнее об этой технологии см. статью "Интеграция VBA в бизнес-приложениях независимых разработчиков", КомпьютерПресс 3/2000). В противном случае команда Install Now не сможет инсталлировать мастер VB Integration Wizard.

Однако недавно обнаружилась проблема: несмотря на то что на компьютер был установлен пакет обновления ServicePack 5 (каждый последующий ServicePack автоматически включает все предыдущие обновления), при установке VBA SDK 6.2 выдавалось сообщение об ошибке.

Причину возникновения этой ситуации (судя по всему, это ошибка Microsoft) и решение проблемы нашел С.Новодворский из Брянска. В реестре номер последнего установленного Service Pack записан в параметре latest ключа HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\VISUALSTUDIO\6.0\SERVICEPACKS. Оказывается, перед установкой SDK нужно просто изменить latest на 3, а после инсталляции — восстановить исходное значение.

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

Совет 372. Как добавить иконку к меню или к кнопке панели инструментов Office

Рассмотрим эту задачу на примере среды Word 97/2000. Как известно, настройка пользовательского интерфейса в пакетах MS Office может выполняться как непосредственно в среде приложения (с помощью диалогового окна "Customize/Настройка"), так и программным образом с применением VBA. В последнем случае можно использовать, например, такую макрокоманду, формирующую новую панель инструментов с кнопкой:

Sub AddCommandBarAndButton()
 '  
 ' AddCommandBarAndButton Macro  
 ' Macro created 21.03.01 by Kolesov Andrei  
 '  
  Dim myBar As CommandBar  
  Dim myControl As CommandBarButton  
   
    ' Создание панели инструментов  
  Set myBar = ActiveDocument.CommandBars.Add(Name:="MyNewBar", _  
    Position:=msoBarTop, Temporary:=True)  
  With myBar  
    .Visible = True  
    .RowIndex = msoBarRowLast  
  End With  
    ' Создание кнопки  
  Set myControl = myBar.Controls.Add _  
   (Type:=msoControlButton, Before:=1)  
  With myControl  
    .Caption = "Новая_Кнопка"  
    .OnAction = "MyNewMacro"  
    .FaceId = 16  
    .Style = msoButtonIconAndCaption  
  End With  
End Sub  

Тут нужно иметь в виду, что при работе с Word панель, формируемая с помощью окна Customize, может храниться в любом документе или шаблоне. В случае программного создания панель сохраняется только в шаблоне Normal.

В приведенном примере мы использовали иконку некоторого встроенного элемента с номером Id = 16 (о проблеме идентификаторов мы поговорим в следующем совете). А как быть, если нужно вставить то или иное собственное изображение?

Здесь может быть предложен такой вариант: создание специальной скрытой панели инструментов, имеющей сугубо вспомогательную функцию — хранение пользовательского набора изображений кнопок. На такую панель (назовем ее IconPanel) можно поместить иконки, отредактированные с помощью встроенного редактора иконок окна Customize. В этом случае создание новой панели инструментов в процессе выполнения VBA-кода будет выглядеть примерно так:

' Создание первой кнопки на новой панели инструментов
' копирование созданной ранее кнопки
CommandBars("IconPanel").Controls(1).Copy bar:=myBar, Before:=1
' коррекция параметров кнопки
Set myControl = myBar.Controls(1)
With myControl
  .Caption = "Новая_Кнопка"
  .OnAction = "MyNewMacro"
  .Style = msoButtonIconAndCaption
End With

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

Совет 373. Как определить ID элементов меню и панелей инструментов

Проблема использования встроенных изображений кнопок заключается в том, что их идентификатор ID в явном виде нигде не указан. Однако его можно определить с помощью VBA-кода, указав в явном виде имя панели и кнопки:

MsgBox CommandBars("Standard").Controls("New Blank Document").ID  

Но здесь имеются две проблемы.

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

Sub OutputControlsID  
  Const MaxId% = 4000  
  Open "c:"ids.txt" For Output As #1  
  ' создаем временную командную панель и включаем  
  ' в нее все возможные элементы и кнопки  
  Set cbr = CommandBars.Add("Временная", msoBarTop, False, True)  
  On Error Resume Next ' игнорируем ошибки  
                       ' (не всем номерам соответствуют встроенные элементы)  
  For I = 1 To MaxID  
    cbr.Controls.Add Id: = 1  
  Next  
  On Error GoTo 0  ' включаем обработку ошибок  
  ' записываем идентификатор и название в файл  
  For Each btn In cbr.Controls  
    Write #1, btn.Id, btn.Caption  
  Next  
  Cbr.Delete  ' удаляем панель  
  Close #1  
End Sub  

Во-вторых, неизвестны номера изображений иконок, представленных в стандартном наборе окна Customize. Тут можно посоветовать просто создать временную кнопку с нужной картинкой и проверить ее код:

MsgBox CommandBars("Temporary").Controls("TestIcon").ID  

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

Совет 374. Как записать иконку для меню Add-Ins в среде VB

Сказанное выше относится к настройке в среде офисных приложений. А как записать иконку для меню или кнопки, используемой для запуска дополнений Add-in в среде самого VB?

Здесь можно предложить такой вариант. Создайте вспомогательную форму, на которой расположите нужное число элементов управления Image. В каждый элемент поместите иконку в виде растрового изображения 16*16. Кстати, сами иконки можно создавать с помощью того же встроенного редактора окна Customize например пакета Word, копируя их через буфер обмена.

Далее требуется написать следующий код в метод AddToAddInCommandBar класса Connect:

Dim cbMenuCommandBar As Office.CommandBarButton
Dim cbMenu As Object
Set cbMenu = VBInstance.CommandBars("Add-Ins")
If Not cbMenu Is Nothing Then ' меню существует
  ' добавляем его к панели инструментов
  Set cbMenuCommandBar = cbMenu.Controls.Add(1)
  CbMenuCommandBar.Caption = "Наш Add-In"
  ' копируем изображение через буфер обмена
  Clipboard.Clear
  Clipboard.SetData = frmAddIns.ImgMenuPic.Picture
  CbMenuCommandBar.PasteFace
  Set AddToAddInCommandBar = cbMenuCommandBar
End If

К сожалению, этот метод для VBA не работает, поскольку объект Clipboard имеется только в VB.

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

Совет 375. Как избавиться от ненужных окон-сообщений

К нам поступил такой вопрос от читателя:

Как написать макрос, чтобы компьютер автоматически отвечал "Нет" на вопросы стандартных диалоговых окон?

Поясню на примере. Скажем, мне надо, чтобы макрос делал что-то в выделенном участке текста Word и не лез в остальной текст. Я записываю макрос стандартным образом (с помощью команды Record New Macro), например прошу найти знаки конца абзаца и заменить их удвоенными знаками конца абзаца. Когда я записываю макрос, то выбираю в стандартном диалоговом окне опцию "заменить все". Затем компьютер сообщает мне, что произвел столько-то замен, и спрашивает, не надо ли провести поиск в оставшемся тексте. Я отвечаю "Нет" и завершаю запись макроса.

Если после этого я выделяю некоторый фрагмент текста и запускаю макрос, то он автоматически заменяет везде в выделенном фрагменте знаки конца абзаца на удвоенные знаки конца абзаца, а затем выводит стандартное окно с предложением произвести замену во всем остальном тексте. Мне это не нужно, так как на самом деле я записываю достаточно большие макросы с разнообразными действиями, и многократно щелкать затем по кнопке "Нет" мне не хочется. Что надо добавить в программу, чтобы избавиться от этой "недоавтоматизации"?

Вот код макроса, о котором шла речь:

Sub Макрос1()
 '  
 ' замена текста в выделенном фрагменте  
  With Selection.Find  
    .Text = "^p"  
    .Replacement.Text = "^p^p"  
    .Forward = True  
    .Wrap = wdFindAsk  
    .Format = False  
  End With  
  Selection.Find.Execute Replace:=wdReplaceAll  
End Sub  

Наш ответ:

В данном случае решить проблему достаточно просто — нужно всего лишь сделать установку свойства

.Wrap = wdFindStop  

При этом поиск будет автоматически прекращен по завершении поиска по выделению. Это очень важный момент — программные возможности работы со стандартными диалогами обычно шире по сравнению с составом опций, выдаваемых в окне. Кстати, возможно, будет полезным в конце данного кода записать еще такую строку:

Selection.EndKey 

Это автоматически уберет выделение фрагмента.

А что же делать в случае невозможности управлять свойством Wrap? Здесь пригодился бы более универсальный вариант — программная имитация нажатия нужной клавиши в окне запроса с помощью оператора SendKeys. При этом код макрокоманды выглядел бы следующим образом:

Sub Макрос1New() ' ' замена текста в выделенном фрагменте 
  With Selection.Find  
    .Text = "^p"  
    .Replacement.Text = "^p^p"  
    .Forward = True  
    .Wrap = wdFindAsk  
    .Format = False  
  End With  
  SendKeys "N" ' посылаем в буфер клавиатуры код  
      ' клавиши N (горячая клавиша No)  
  Selection.Find.Execute Replace:=wdReplaceAll  
  Selection.EndKey ' убираем выделение  
End Sub  

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

MyText$ = Chr$(13) ' "конец абзаца"
MyReplacementText = Chr$(13) & Chr$(13)  ' два знака "конец абзаца"
Selection.Text = Replace(Selection.Text, MyText$, MyReplacementText)

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

Совет 376. Используйте XML Parser

В статье "Использование XML DOM в VB и MS Office/VBA" мы рассматривали некоторые возможности взаимодействия с XML-документами. Работа выполнялась с помощью набора объектов библиотеки Microsoft XML 2.0 (MSXML.DLL), которая сейчас обычно называется MS XML Parser.

Это название отражает основное назначение библиотеки (parse — выполнять грамматический разбор), хотя в действительности ее функции выходят за рамки грамматического разбора документа, обеспечивая широкий спектр операций по манипуляциям со структурой и содержимым DOM-документов. Фактически XML Parser предоставляет разработчику приложений механизм создания DOM-документа в виде программного интерфейса взаимодействия с этим документом, а также преобразования его в XML-формат и обратно. В связи с этим хотелось бы сделать небольшое дополнение к упомянутой выше статье.

Одним из основных элементов технологии платформенно-независимого информационного взаимодействия различных приложений является использование объектной модели документов XML (XML Document Object Model, XML DOM), стандарт которой принят комитетом World Wide Web Consortium (W3C). Интерфейс DOM обеспечивает доступ к иерархической структуре, содержимому и стилям документа независимо от платформы и языка программирования.

Следует четко определиться в соотношениях понятий "DOM-документ" и "XML-документ", которые, с одной стороны, почти тождественны, с другой — качественно различны. DOM-документ, создаваемый приложением, является внутренним объектом последнего, и в общем случае о его физической реализации никому ничего не известно (также как мы работаем с документами Word, ничего не зная о формате его хранения). Содержимое DOM-документа становится доступным для всех остальных приложений путем сохранения его в формате XML-файла. Таким образом, XML-документ является представлением DOM-документа на языке XML.

На примере Visual Basic логика работы с этими документами выглядит следующим образом:

Set xmlDoc = New DOMDocument  ' создание нового объекта
' далее - работа по формированию документа
...
xmlDoc.Save "File.xml"    ' сохранение в виде XML-файла
... xmlDoc.Load "NewFile.xml"
' чтение XML-файла
' далее выполняется работа с DOM-объектом

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

Совет 377. Используйте новшества MSXML 3.0

В конце 2000 года Microsoft выпустила новую версию MS XML Parser 3.0 (MSXML3.DLL), призванную заменить MSXML 2.0 и MSXML 2.5, которые поставлялись соответственно в составе Internet Explorer 5.0 и Windows 2000. MSXML 3.0 предоставляет следующие новые функции и возможности по сравнению с версией 2.5:

Следует обратить внимание на особенности установки и применения MSXML 3.0. Будучи инсталлирована на компьютер, она не заменяет автоматически предыдущую версию MSXML 2.х — оба варианта библиотеки могут одновременно работать с одним приложением. Например, если некоторое VB-приложение работало с MSXML 2.0, используя следующий код:

Dim xml As DOMDocument
Set xmlDoc = New DOMDocument

то для переключения на работу с MSXML 3.0 нужно заменить ссылку с MSXML 2.0 на версию 3.0. Однако можно использовать ссылки на обе библиотеки одновременно; в этом случае приведенный выше код будет соответствовать MSXML 2.0, а для работы с MSXML 3.0 потребуется такая конструкция:

Dim xml As MSXML2.DOMDocument  ' "2" указывает на стандарт    SAX2
Set xmlDoc = New MSXML2.DOMDocument

После установки MSXML 3.0 все компоненты операционной системы (Windows 9x, Windows NT и Windows 2000), в том числе Internet Explorer, продолжают работать с предыдущей версией MSXML 2.x до тех пор, пока не будет выполнена "ручная" замена версий с помощью специальной утилиты XMLINST.EXE.

Загрузить библиотеку MSXML 3.0, набор для разработчика MSXML SDK 3.0 и утилиту XMLINST.EXE можно по адресу www.msdn.microsoft.com/xml.

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

Совет 378. Установка кодировки в MS XML Parser 3.0

Одним из простых, но приятных новшеств новой версии XML Parser является возможность выбора кодировки для записи данных. Ранее использовалась только двухбайтовая кодировка UTF-8, поэтому в обычном текстовом редакторе работать (читать, редактировать) с кириллицей было невозможно.

Теперь же можно использовать привычную Windows-кодировку, указав соответствующий параметр в строке инициализации документа:

Dim xmlDoc As DOMDocument
 Set xmlDoc = New DOMDocument  
 xmlDoc.loadXML "<?xml version='1.0' encoding='Windows-1251'?>"  

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