Советы тем, кто программирует на VB & VBA
СОВЕТ 366. КАК УЗНАТЬ АДРЕС ОТПРАВИТЕЛЯ ПИСЬМА В OUTLOOK 2000
СОВЕТ 367. ПОИСК ФАЙЛОВ С ПОМОЩЬЮ ОБЪЕКТА FILESEARCH
СОВЕТ 368. СОРТИРОВКА СОДЕРЖИМОГО LISTVIEW
СОВЕТ 369. КАК ОПРЕДЕЛИТЬ ИМЯ ДИСКОВОДА CD-ROM С ПОМОЩЬЮ FILESYSTEMOBJECT
СОВЕТ 370. КАК ПЕРЕДАТЬ ТЕКСТ ИЗ RICH TEXTBOX В MICROSOFT WORD
СОВЕТ 371. КАК УСТАНОВИТЬ VBA SDK
СОВЕТ 372. КАК ДОБАВИТЬ ИКОНКУ К МЕНЮ ИЛИ К КНОПКЕ ПАНЕЛИ ИНСТРУМЕНТОВ OFFICE
СОВЕТ 373. КАК ОПРЕДЕЛИТЬ ID ЭЛЕМЕНТОВ МЕНЮ И ПАНЕЛЕЙ ИНСТРУМЕНТОВ
СОВЕТ 374. КАК ЗАПИСАТЬ ИКОНКУ ДЛЯ МЕНЮ ADD-INS В СРЕДЕ VB
СОВЕТ 375. КАК ИЗБАВИТЬСЯ ОТ НЕНУЖНЫХ ОКОН-СООБЩЕНИЙ
СОВЕТ 376. ИСПОЛЬЗУЙТЕ XML PARSER
СОВЕТ 377. ИСПОЛЬЗУЙТЕ НОВШЕСТВА MSXML 3.0
СОВЕТ 378. УСТАНОВКА КОДИРОВКИ В MS XML PARSER 3.0
Совет 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» (КомпьютерПресс, № 12’2000) мы рассматривали некоторые возможности взаимодействия с XML-документами. Работа выполнялась с помощью набора объектов библиотеки Microsoft XML 2.0 (MSXML.DLL), которая сейчас обычно называется MS XML Parser. Это название отражает основное назначение библиотеки (parse — выполнять грамматический разбор), хотя в действительности ее функции выходят за рамки грамматического разбора документа, обеспечивая широкий спектр операций по манипуляциям со структурой и содержимым DOM-документов. Фактически XML Parser предоставляет разработчику приложений механизм создания DOM-документа в виде программного интерфейса взаимодействия с этим документом, а также преобразования его в XML-формат и обратно. В связи с этим хотелось бы сделать небольшое дополнение к упомянутой выше статье.
Одним из основных элементов технологии платформенно-независимого информационного взаимодействия различных приложений является использование объектной модели документов (Document Object Model, 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:
- полное соответствие W3C-стандартам для технологий Extensible Stylesheet Language Trasformations (XSLT) и XML Path Language (XPath);
- полное соответствие интерфейсу COM/Microsoft ActiveX Simple API for XML (SAX), включая также ряд вспомогательных объектов;
- поддержка безопасного HTTP-доступа (server-safe HTTP access) со стороны серверных приложений;
- ряд улучшений для поддержки DOM и национальных языков;
- высокая степень адаптации к стандартам W3C XML 1.0 и Namespace 1.0, а также к требованиям тестового набора OASIS (Organization for the Advancement of Structural Infomation Standards).
Следует обратить внимание на особенности установки и применения 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 можно по адресу http://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'?>"
КомпьютерПресс 5'2001