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

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

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

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


Совет 290. Как заполнить поля электронного сообщения

ShellExecute является одной из наиболее гибких функций в Win32 API. С ее помощью можно передавать любое имя файла, а если расширение файла связано с какой-либо из программ, зарегистрированных на машине пользователя, то запускается соответствующее приложение, которое выводит или проигрывает указанный файл.

Здесь мы покажем, как использовать функцию ShellExecute для отправки электронных сообщений. Вы сможете задавать не только адрес получателя, но и списки получателей (CC и BCC), тему и текст сообщения, а также вставлять файл или его часть в отправляемое сообщение. Для этого необходимо создать строковую переменную, добавить список основных адресов (отделенных друг от друга точкой с запятой) и знак вопроса:

для копий CC (Копия): &CC= (список получателей)
для невидимых (слепых) копий: &BCC= (список получателей)
для темы сообщения: &Subject= (тема сообщения)
для текста сообщения: &Body= (текст сообщения)
для присоединения файла: &Attach= (путь к файлу, заключенный в кавычки)

Продемонстрируем это на примере. Создайте новый VB-проект, добавьте форму и разместите на ней шесть текстовых полей и одну командную кнопку cmdSendIt (см. рис. 290).

Рис. 290

Напишите такой код в разделе Declarations:

Private Declare Function ShellExecute Lib _
  "shell32.dll" Alias "ShellExecute" _
     (ByVal hWnd As Long, ByVal lpOperation As _
     String, ByVal lpFile As String, ByVal _
     lpParameters As String, ByVal lpDirectory _
     As String, ByVal nShowCmd As Long) As Long
Private Const SW_SHOWNORMAL = 1

Затем введите следующий код для события Click командной кнопки:

Private Sub cmdSendIt_Click()
  Dim sText As String
  Dim sAddedText As String
  If Len(txtMainAddresses) Then
       sText = txtMainAddresses
  End If
  If Len(txtCC) Then sAddedText = sAddedText & "&CC=" & txtCC
  If Len(txtBCC) Then sAddedText = sAddedText & "&BCC=" & txtBCC
  If Len(txtSubject) Then _
       sAddedText = sAddedText & "&Subject=" & txtSubject
  If Len(txtBody) Then _
       sAddedText = sAddedText & "&Body=" & txtBody
  If Len(txtAttachementFileLocation) Then _
       sAddedText = sAddedText & "&Attach=" & Chr(34) & _
            txtAttachementFileLocation & Chr(34)
  sText = "mailto:" & sText
  If Len(sAddedText) <> 0 Then Mid$(sAddedText, 1, 1) = "?"
  sText = sText & sAddedText
  If Len(sText) Then _
    Call ShellExecute(Me.hWnd, "open", sText, _
     vbNullString, vbNullString, SW_SHOWNORMAL)
End Sub

Здесь следует обратить внимание на два момента:

  1. Между знаками "амперсанд" (&) и "тэг" или знаками "тэг" и "равно" не нужно ставить пробелы.
  2. Из-за отсутствия возможностей форматирования текст сообщения будет состоять из одного параграфа.

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

Примечание. Полная функциональность полей электронного сообщения может быть достигнута только в почтовых клиентах, совместимых с Microsoft Exchange. С другими почтовыми клиентами некоторые или даже все эти поля могут не работать.

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

Совет 291. Как открыть VB-файл в Notepad или в любом другом текстовом редакторе

Предположим, вы хотите сделать так, чтобы с помощью щелчка правой кнопки мыши можно было открыть VB-файл в редакторе Notepad и скопировать фрагмент кода для другого приложения, например для того, над которым вы сейчас работаете. Попробуйте сделать следующее.

Создайте текстовый файл с именем FormEdit.reg, введите в него такой код, сохраните, а затем закройте его:

REGEDIT4
[HKEY_CLASSES_ROOT\VisualBasic.Form\shell\edit]
@="&Edit"
[HKEY_CLASSES_ROOT\VisualBasic.Form\shell\edit\command]
@="C:\Windows\Notepad.exe %1"

Дважды щелкните мышью на этом файле, и его содержимое автоматически загрузимся в Системный Реестр. Теперь щелкните правой кнопкой мыши на любом FRM-файле, и в появившемся "быстром" меню вы увидите команду Edit. Выполните ту же самую операцию для других текстовых VB-файлов — VisualBasic.ClassModule, VisualBasic.Module и VisualBasic.Project, а затем с помощью программы Regedit.exe в Windows проверьте полученные результаты.

Если вместо Notepad вы хотите использовать Word или любой другой текстовый редактор, напишите примерно такую командную строку (для Word):

@="c:\\Program Files\\Microsoft Office\\Office\\Winword.exe %1"

Здесь следует быть особенно внимательными — используйте две обратные косые черты и, кроме того, не ошибитесь, указывая полный путь к файлу Winword.exe.

Хотя VB4, VB5 и VB6 имеют различные точки входа в Реестре, подобную методику можно применять для любого текстового VB-файла, включая BAS, CLS, FRM и VBP. Она не подходит для FRX- или других двоичных файлов, но с ее помощью можно реализовать просмотр HTML-файлов. Указав путь к используемому по умолчанию браузеру, можно, щелкнув правой кнопкой мыши на любом HTML-файле, редактировать его как простой текст. Кроме того, можно настроить браузер на просмотр GIF-файлов, чтобы увидеть, что они собой представляют. Но самое главное — всякий раз, работая с Реестром, будьте крайне осторожны.

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

Совет 292. Как ускорить операцию деления чисел с плавающей запятой

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

X / Y

Выполните:

X * (1 / Y)

Чтобы увидеть, как это работает на практике, создайте новый проект в VB и введите следующий код в событие Form_Click:

Private Declare Function GetTickCount _
     Lib "kernel32" () As Long
Const NORMAL As Double = 1453
Const RECIPROCAL As Double = 1 / NORMAL
Const TOTAL_COUNT As Long = 10000000
Private Sub Form_Click()
  Dim dblRes As Double
  Dim lngC As Long
  Dim lngStart As Long
  '
  On Error GoTo Error_Normal
  '
  lngStart = GetTickCount
  For lngC = 1 To TOTAL_COUNT
    dblRes = Rnd / NORMAL
  Next lngC
  MsgBox "Обычное время: " & GetTickCount - lngStart

  lngStart = GetTickCount
  For lngC = 1 To TOTAL_COUNT
    dblRes = Rnd * RECIPROCAL
  Next lngC
  MsgBox "Время для обратной величины: " _
     & GetTickCount - lngStart
Exit Sub
Error_Normal:
  MsgBox Err.Number & " - " & Err.Description
End Sub

Вы обнаружите, что скорость выполнения вычислений увеличится примерно на 15% при использовании метода умножения на обратную величину. Тем не менее будьте осторожны при округлении чисел — так, 3 / 3 = 1, но 3 * (0,333333...) = 0,999999....

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

Совет 293. Как ускорить выполнение операций ввода-вывода для файлов

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

Для текстовых файлов можно предложить такие две процедуры чтения и записи файлов:

Public Function ReadFile(FileName _
       As String) As String
  Dim FileNumber As Integer
  FileNumber = FreeFile
  Open FileName For Input As #FileNumber
  ReadFile = Input(LOF(FileNumber), FileNumber)
  Close #FileNumber
End Function
Public Sub WriteFile(FileName As _
       String, Contents As String)
  Dim FileNumber As Integer
  FileNumber = FreeFile
  Open FileName For Output As #FileNumber
  Print #FileNumber, Contents;
  Close #FileNumber
End Sub

Обратите внимание, что перед открытием файлов мы получаем свободный номер файла с помощью функции FreeFile. С применением таких функций операция копирования двух файлов принимает такой вид:

Call WriteFile("c:\b.txt", ReadFile("c:\a.txt"))

Для копирования файлов произвольного формата следует использовать тип файлов Binary. Тогда операции чтения-записи будут выглядеть так:

Open FileInput$ For Binary As #FileInp
FileString$ = Space$(LOF(FileInp))
Get #FileInp,, FileString$
ReadFile = FileString$
...
Open FileOutput$ For Bi As #FileOut
Put #FileOut,, FileString$

Но в этом случае нужно сначала сделать проверку — может быть, выходной файл уже существует (если это так, то в него просто перепишутся первые Len(FileString$) символов).

Однако следует иметь в виду, что для точного чтения содержимого произвольных файлов использование строковой переменной не очень хорошо подходит. Дело в том, что в этом случае при чтении производится преобразование байтов из однобайтовой ANSII-кодировки в двухбайтовую Unicode (подробнее об этом см. КомпьютерПресс N 10'99, CD-ROM). При записи производится обратное преобразование.

Поэтому для создания точной копии содержимого файлов следует использовать байтовый динамический массив:

ReDim ArrByte (1 To LOF(FileNumber))
Get #FileInp,, ArrByte
...
Get #FileOut,, ArrByte

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

Совет 294. Как создать дополнение, закрывающее все окна проекта

При создании VB-проекта вы наверняка открываете множество различных окон, особенно выполняя операции поиска/замены внутри программного кода. В больших проектах количество открытых окон вырастает настолько, что становится обременительным закрывать их одно за другим. Поэтому мы предлагаем создать простое дополнение, которое бы выполняло эту работу за вас.

Создайте новый проект типа AddIn (значок Addin в окне New Project). Вы получите шаблон MyAddIn, в котором содержится некоторый программный код для построения дополнения. В код формы frmAddIn введите следующее:

Private Sub Form_Load()
  Dim w As Window
  For Each w In VBInstance.Windows
    ' закрывает все видимые
    ' окна кода и форм
    If (w.Type = vbext_wt_CodeWindow _
      Or w.Type = vbext_wt_Designer) _
       And w.Visible Then w.Close
    End If
  Next
  Unload Me
End Sub

В окне Object Browser щелкните правой кнопкой мыши на проекте MyAddIn. В появившемся "быстром меню" выберите команду Properties и измените имя и описание дополнения. Затем в коде шаблона замените My Add-In на то имя, которое бы вы хотели видеть в меню Add-Ins в среде разработки VBE. Если вы работаете в VB6, то помимо этого необходимо еще поменять имя дополнения в конструкторе AddInDesigner. Создайте DLL-библиотеку (команда File|Make MyAddIn.dll), и ваше дополнение должно появиться в списке в Add-In Manager. Добавьте его к командам меню Add-Ins, а затем запустите — все открытые окна проекта закроются в одно мгновение!

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

Совет 295. Используйте ключевое слово WithEvents для связи форм MDI и MDIChild

Здесь приводится изящный способ передачи событий, таких как щелчки на панели инструментов или выделение команд меню, из родительской MDI-формы в активную дочернюю MDIChild-форму в многодокументном приложении. Предположим, что MDI-форма содержит элемент управления Toolbar с именем tbrMain, для которого введите следующий код:

Event ButtonClick(strKey As String)
Private Sub tbrMain_ButtonClick(ByVal _
     Button As MSComctlLib.Button)
     RaiseEvent ButtonClick(Button.Key)
End Sub

Затем напишите такой код для каждой MDIChild-формы, которая должна получить событие ButtonClick:

Private WithEvents m_mdiParent As mdiParent
Private Sub tbrMain_ButtonClick(ByVal _
  Button As MSComctlLib.Button)
  RaiseEvent ButtonClick(Button.Key)
End Sub

Private Sub Form_Acitivate()
  Set m_mdiParent = mdiParent
End Sub
Private Sub Form_Deactivate()
  Set m_mdiParent = Nothing
End Sub

Private Sub m_mdiParent_ButtonClick (strKey As String)
  ' Пример кода, в котором значения
  ' Button.Key соответствуют кнопкам
  ' New, Change, Delete и Save
  Select Case strKey
    Case "New"
      PerformNewAction
    Case "Change"
      PerformChangeAction
    Case "Delete"
      PerformDeleteAction
    Case "Save"
      PerformSaveAction
  End Select
End Sub

Использование этой подпрограммы аналогично объявлению элемента управления с именем m_mdiParent, у которого есть событие ButtonClick. Используйте события Activate и Deactivate, чтобы форма MDIChild являлась единственной, которая бы получала событие ButtonClick.

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

Совет 296. Помните об операторе Option Base

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

ReDim MyArray (LowBoundary To UpBoundary)

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

ReDim MyArray (UpBoundary)
Dim MyArrayStat(100)

Но чему же равна нижняя граница и сколько элементов на самом деле содержится в массиве?

В языках программирования используется два варианта соглашений: например, в Фортране — нумерация традиционно начинается с единицы, а в Бейсике — с нуля. Во всех версиях Microsoft Basic (Quick for DOS, Visual for Windows) программист может управлять установкой значения нижней границы по умолчанию с помощью оператора Option Base. Он помещается в секции Declarations в начале программного модуля (форма, модуль кода, модуль класса и пр.) и его действие распространяется на все объявления массивов внутри модуля.

Таким образом, значение нижней границы для приведенного выше примера Dim MyArrayStat(100) будет следующим:

Все это нужно иметь в виду, если вы вставляете фрагмент программного кода в какие-либо модули (например, из своей библиотеки повторно используемых процедур), — значение нижней границы будет зависеть от наличия в данном модуле оператора Option Base.

Совет: Если для программы принципиально важно наличие или отсутствие нулевого индекса, то указывайте обе границы индекса в явном виде.

Еще одно замечание. В VB 5/6 имеется возможность автоматического создания и заполнения байтового массива при перезаписи в него строковой переменной:

Dim arrByte () As Byte
Source$ = "Строковая переменная"
arrByte = Source$

В этом случае всегда создается массив размерностью от 0 до (LenB(Source$)-1) независимо от наличия/отсутствия оператора Option Base.

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

Совет 297. Экспорт таблиц в виде текстовых файлов

Здесь приведена простая процедура, которая выводит в виде текстового файла записи из таблицы базы данных или из таблицы, сформированной в результате SQL-запроса. Эта информация потом может быть считана любым текстовым редактором или программой электронных таблиц. В этом примере Db — это глобальная переменная объекта, которая должна быть до обращения к процедуре определена как база данных. sSource — имя таблицы или SQL-запрос. Кроме имени выходного файла пользователь должен также задать код разделителя полей записи в текстовом файле:

Public Function TableToSpreadsheetMy(sSource As String, _
  sFile As String, sSeparator As String) As Boolean
  ' Включение обработки ошибок
  On Error GoTo TableToSpreadsheet_Err
  '
  ' Синтаксис обращения:
  ' If TableToSpreadsheet("SELECT * FROM _
  ' Authors", "C:\Temp\Authors.csv", Chr$(9)) = True _
  ' Then....
  '
  Dim rsTemp As Recordset
  Dim sHeader As String
  Dim sRow As String
  Dim i As Integer, nFile As Integer
  ' Формирование набора данных
  ' Глобальная объект-переменная Db должна
  ' быть открыта ранее как база данных
  Set rsTemp = Db.OpenRecordset(sSource)
  With rsTemp
    TableToSpreadsheet = False
    ' Есть ли записи в наборе?
    If .RecordCount > 0 Then
      ' Создание выходного файла
      nFile = FreeFile
      Open sFile For Output As #nFile
      ' Вывод имен полей таблицы
      sHeader = .Fields(0).Name
      If .Field.Count > 0 Then
        For i = 1 To .Fields.Count - 1
          sHeader = sHeader & sSeparator & .Fields(i).Name
        Next i
      End If
      Print #nFile, sHeader
      ' Вывод содержимого полей таблицы
      .MoveFirst
      Do Until .EOF
        sRow = .Fields(0).Value
        If .Field.Count > 0 Then
          For i = 1 To .Fields.Count - 1
            sRow = sRow & sSeparator & .Fields(i).Value
          Next i
        End If
        Print #nFile, sRow
        .MoveNext
      Loop
      Close #nFile ' Target file is complete
      TableToSpreadsheet = True
    End If
    .Close
  End With
  Set rsTemp = Nothing ' закрываем временный объект
  Exit Function
TableToSpreadsheet_Err:
  LogIt "TableToSpreadsheet : " & Err.Description
  ' Запись информации об ошибке
  Resume Next
End Function

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