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

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

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

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


Совет 271. Избегайте использования переменных типа Variant

Наш Совет 259 (об использовании операторов Print # и Write #) был основан на замечании, которое прислал наш читатель Константин Абакумов по поводу конструкций, использованных нами в коде утилиты Summa.vbp (сумма прописью, Cовет 228). Кроме того, он обнаружил еще одну "дырочку" в этой программе: оказалось, что следующая конструкция у него давала неверный результат:

Dim InVal
...
Input #1, InVal                  ' здесь у Константина не срабатывала
If InVal = True Then Call ...    ' проверка при вводе значения True
...

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

If InVal Then Call ...    ' при вводе значения True

Совершенно очевидно, что оба варианта в принципе тождественны. Не говоря уже о том, что у нас исходный вариант надежно работал. В чем же дело?

Проблема заключалась в том, что мы работали в среде VB5 (мы сознательно используем эту версию, если нет нужды в применении новшеств VB6), а наш читатель — в VB6. Судя по всему, в VB6 имеется ошибка, в результате чего внешне правильная конструкция оказалась неработоспособной.

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

Мы считаем, что VB (как и многие другие современные системы программирования) обладает серьезным технологическим недостатком, который заключается в широком использовании неявного преобразования типов данных. С точки зрения классических принципов программирования конструкция типа:

srtVal$ = IntVal%

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

SrtVal$ = Str$(IntVal%)

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

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

sVal! = 1/3*6

в зависимости от желания разработчика компилятора может быть равным и 0 и 2. И в обоих случаях разработчик будет прав, так как нет жестких правил порядка преобразования данных.

Что же касается конструкции (вполне допустимой в VB) типа

strTrue$ = "True"
If strTrue$=True Then ...,

то она вообще представляется кошмарной.

Логическим выводом из вышесказанного является то, что появление некоторого универсального типа данных, которым в VB является Variant, является грубым нарушением классических принципов программирования. Желание помочь программистам ("вам не нужно думать о форме представления данных") на самом деле усложнило разработку программ.

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

Каемся: мы были не правы, что использовали в утилите Summa общую переменную InVar (по умолчанию — Variart) для обработки ввода нескольких параметров разных типов данных. Мы исправили код, который теперь выглядит так:

Dim InBool As Boolean, InText$, InVal%
  Input #1, InBool: If InBool Then Call LabelSet(1)
  Input #1, InText: txtW1.Text = InText

ИЗБЕГАЙТЕ использования переменных типа Variant! Применяйте фиксированные типы переменных и функции явного преобразования типов данных. Если у вас есть доводы против этого тезиса — присылайте нам свои соображения.

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

Совет 272. Вывод анимационных GIF-файлов в VB

Хотя элемент управления Picture позволяет отображать на экране графические изображения, он выводит только первый рисунок из анимационного GIF-файла. Чтобы полностью показать такой файл, не создавая заново всю последовательность изображений, можно воспользоваться элементом управления WebBrowser (который, правда, недоступен на компьютерах, где отсутствует Internet Explorer 3.0 или выше). Для этого установите компонент Microsoft Internet Controls (команда Project|Components), и тогда на панели инструментов Visual Basic появится элемент управления WebBrowser. Поместите его на форму, а затем в событии Form_Load() введите следующий код:

WebBrowser1.Navigate "filespec"

где filespec — полное имя GIF-файла (включая путь к нему) или URL-адрес. Запустите программу на выполнение, и Visual Basic выведет указанный файл.

К сожалению, WebBrowser одновременно выведет вертикальную линейку прокрутки на правой стороне формы, что не вполне сочетается с графическим изображением. Чтобы отключить ее, как ни удивительно, можно применить тот же самый метод, что обычно реализуется в HTML-коде:

WebBrowser1.Navigate @about:<html>" & _
    "<body scroll='no'><img src='filespec'>" & _
    </img></body></html>"

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

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

Совет 273. Как прочитать значения Системного Реестра в VB без помощи функций API

В некоторых случаях бывает необходимо управлять Реестром Windows в VB, не прибегая к использованию функций API. Для этого существует библиотека Registry Access Functions (RegObj.dll), позволяющая создавать объект Registry, при помощи которого можно управлять конкретными ключами. Это может пригодиться, например, в следующем случае.

В предыдущем Совете мы продемонстрировали использование элемента управления WebBrowser. Однако, как мы уже отмечали, WebBrowser доступен только в том случае, если на машине-приемнике также установлен Internet Explorer. Поэтому, прежде чем приступить к выводу анимации, было бы целесообразно определить, имеется там IE или нет. Для этого вначале установите в своем проекте ссылку к библиотеке Registry Access Functions (команда Project|References) и введите код, аналогичный тому, что приводится здесь:

Dim myReg As New Registry, KeyFound As Boolean
Dim HasIE As Boolean, sValue As String
    sValue = ""
    HasIE = False
    KeyFound = myReg.GetKeyValue(HKEY_LOCAL_MACHINE, _
        "Software\Microsoft\Windows\CurrentVersion\" & _
        "App Paths\IEXPLORE.EXE", "Path", sValue)
    If KeyFound Then HasIE = (sValue <> "")
     'выводит полное название каталога,
     'где установлен IE
    If HasIE Then MsgBox sValue
    Set myReg = Nothing

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

Совет 274. Реализация проверки правописания в RTF-документах в VB

Для проверки правописания в RTF-документах можно было бы прибегнуть к следующему способу: внедрить Word в свое приложение (этот вариант приведен ниже, в Совете 282). Однако мы рекомендуем воспользоваться элементом управления WebBrowser, о котором шла речь в двух предыдущих Советах и с помощью которого можно организовать такую проверку. Для этого вам понадобится метод ExecWB() с флагом OLECMDID_SPELL. В качестве иллюстрации поместите WebBrowser на форму и введите такой код в событие Form_Load():

WebBrowser1.Navigate "filespec"

Замените filespec любой строкой, которая указывает путь к RTF-файлу. Затем добавьте к форме командную кнопку, для которой в событии Click напишите следующий код:

WebBrowser1.ExecWB OLECMDID_SPELL, OLECMDEXECOPT_DODEFAULT

Запустите проект на выполнение. Элемент управления WebBrowser выведет указанный RTF-документ, и, когда вы щелкнете командную кнопку, VB активизирует установленную на вашем компьютере программу проверки правописания.

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

Совет 275. Как реализовать ссылки к набору записей объекта Command, содержащегося в конструкторе Data Environment

Каждый объект Command в новом конструкторе Data Environment в VB 6.0 имеет соответствующий ему набор записей. Для того чтобы в коде программы сослаться на этот объект, необходимо добавить к его имени префикс rs. Так, если конструктор Data Environment содержит объект Command с именем cmdSomeQuery, то для ссылки к набору записей этого объекта напишите примерно такую строку кода:

Set rst = DataEnvironment1.rscmdSomeQuery

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

Совет 276. Сжимайте длинные имена файлов с помощью библиотеки SHLWAPI

Использование функции PathCompactPath из библиотеки SHLWAPI — один из способов сжатия длинных имен файлов, благодаря которому часть имени слева заменяется на многоточие (...). Для работы с данной API-функцией требуется описать ее следующим образом:

Private Declare Function _
PathCompactPath Lib "shlwapi"_
    Alias "PathCompactPathA" _
    (ByVal hDC As Long, ByVal _
    lpszPath As String, _
    ByVal dx As Long) As Long

Как видно из этого описания, функция PathCompactPath содержит три аргумента: первый из них задает дескриптор контекста устройства (device context handle), второй — имя файла, которое будет сжиматься, и, наконец, последний — ширину в пикселах того компонента на форме, куда мы хотим поместить это имя. Так, чтобы вывести компактное имя файла в метке с именем lblEllipsis, напишем следующий код для события Click() командной кнопки:

Private Sub Command1_Click()
Dim lhDC As Long, lCtlWidth As Long
Dim FileSpec As String
    FileSpec = "C:\MyFolder\VisualBasic\" & _
        "MyReallyWayTooLongFolderName\ButWhoCares\IhaveTheAPI.doc"
    Me.ScaleMode = vbPixels
    lCtlWidth = lblEllipsis.Width - Me.DrawWidth
    lhDC = Me.hDC
    PathCompactPath lhDC, FileSpec, lCtlWidth
    lblEllipsis.Caption = FileSpec
End Sub

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

Совет 277. Еще один способ чтения нескольких элементов списка

Обычное правило чтения выделенных элементов окна списка, поддерживающего выбор нескольких позиций (иными словами, того, у которого свойство MultiSelect отлично от 0), гласит, что необходимо организовать в цикле просмотр всех элементов и проверить у них значение свойства Selected. Однако такой подход (как и при работе с любыми циклами) может значительно снизить быстродействие создаваемого вами приложения, особенно на менее скоростных процессорах. В качестве более быстрой и элегантной альтернативыпредлагаем вам воспользоваться API-функцией SendMessage().

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

Private Declare Function SendMessage Lib "user32" _
   Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg _
    As Long, ByVal wParam As Long, lParam As Any) As Long

Поскольку мы хотим прочитать все выделенные элементы списка, необходимо отправить константу LB_GETSELITEMS в качестве аргумента wMsg и описать ее так:

Private Const LB_GETSELITEMS = &H191

По существу сообщение LB_GETSELITEMS заполняет массив значениями индексов всех выделенных элементов. В результате вместе с функцией SendMessage() необходимо передать два дополнительных аргумента. Первый из них должен содержать максимальное количество выделенных элементов, получить которое можно с помощью свойства SelCount элемента управления ListBox. Второй же аргумент должен хранить переменную массива, которую мы хотим заполнить значениями индексов.

Следующий пример демонстрирует, как можно использовать функцию SendMessage(). Поместите на форму элемент управления ListBox1, установите его свойство MultiSelect как 1-Simple и заполните список. Затем добавьте командную кнопку Command1 и напишите следующий код для ее события Click:

Dim ItemIndexes() As Long, x As Integer, iNumItems As Integer
NumItems = ListBox1.SelCount
If iNumItems Then
    ReDim ItemIndexes(iNumItems - 1)
    SendMessage ListBox1.hwnd, LB_GETSELITEMS, iNumItems, _
    ItemIndexes(0)
End If
For x = 0 To iNumItems - 1
    MsgBox ListBox1.List(ItemIndexes(x))
Next x

Обратите внимание, что переменная iNumItems, после того как она передается в функцию SendMessagen, хранит общее число выделенных элементов списка, а массив ItemIndexes — значения индексов выделенных элементов. При этом вы должны передать указатель к массиву ItemIndexes, а не сам массив, то есть в функцию SendMessage передается ItemIndexes(0), а не ItemIndexes().

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

Совет 278. Как можно просто связать элементы двух списков

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

Array(3, "Конфеты", "Мишка на Севере", "Белочка", "Красная Шапочка")

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

Чтобы протестировать приведенный ниже код, создайте проект Standard EXE, а потом поместите на форму два окна списка, оставив без изменения их имена List1 и List2:

Private varArray As Variant
Private varSubArray As Variant
Private Sub Form_Initialize()
    varArray = Array(Array(4, "Фрукты", 'Яблоки", _
    "Апельсины", "Персики", "Груши", Array(5, _
    "Овощи", "Горох", "Бобы", "Кукуруза", "Свекла", _
    "Лук"), Array(3, "Ежедневные продукты", _
    "Молоко", "Сметана", "Масло"))
End Sub
Private Sub Form_Load()
  Dim intIndex1 As Integer
  With List1
    For intIndex1 = 0 To UBound(varArray)
      varSubArray = varArray(intIndex1)
      .AddItem varSubArray(1)
    Next intIndex1
   .ListIndex = 0
  End With
End Sub

Private Sub List1_Click()
  Dim intIndex2 As Integer
  With List2
    varSubArray = varArray(List1.ListIndex)
    .Clear
    For intIndex2 = 0 To varSubArray(0) - 1
      .AddItem varSubArray(intIndex2 + 2)
    Next intIndex2
    .ListIndex = 0
    .Refresh
  End With
End Sub

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

Совет 279. Как реализовать работу с несколькими дисководами CD-ROM с помощью MCI-интерфейса

Интерфейс MCI (Media Control Interface — Интерфейс управления средой) позволяет поддерживать несколько устройств для чтения аудиокомпакт-дисков. Для этого следует просто указать имя дисковода в команде MCI Open. Вначале поместите окно списка List1 на форму, а затем, чтобы определить, какие дисководы предназначены для чтения компакт-дисков, напишите в разделе General Declarations формы такой код:

Private Declare Function GetDriveType Lib _
    "kernel32" Alias "GetDriveTypeA" (ByVal _
    nDrive As String) As Long
Private Declare Function mciSendString Lib _
    "winmm.dll" Alias "mciSendStringA" (ByVal _
    lpstrCommand As String, ByVal lpstrReturnString _
    As String, ByVal uReturnLength As Long, ByVal _
    hWndCallback As Long) As Long
Private Const DRIVE_CDROM = 5

Подпрограмма Form_Load заполняет окно списка обнаруженными на компьютере именами дисководов CD-ROM:

Sub Form_Load()
  Dim k As Long
  For k = Asc("A") To Asc("Z")
    If GetDriveType(Chr$(k) & ":") = DRIVE_CDROM Then
      List1.AddItem Chr$(k) & ":"
    End If
  Next
End Sub

А чтобы действительно открыть дверцу дисковода, куда можно будет вставить компакт-диск, введите такой код в событие List1_dblClick:

Private Sub List1_DblClick()
  mciSendString "open"; & _
    List1.List(List1.ListIndex) & _
    "type cdaudio alias cdaudi", _
        vbNullString, 0, 0
    mciSendString "set cdaudio door open", _
        vbNullString, 0, 0
    mciSendString "close cdaudio", _
        vbNullString, 0, 0
End Sub

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

Совет 280. Как обрабатывать события, связанные с изменением свойств шрифта

Предположим, вы хотите выполнить какое-нибудь действие при изменении любых свойств шрифта конкретного элемента управления или формы. Для этого вам понадобится ключевое слово WithEvents. (Не забудьте установить ссылку OLE Automation в диалоговом окне References с помощью команды Project|References):

Private WithEvents fntAny As StdFont

Затем напишите подпрограмму, которая будет реагировать на изменения свойств шрифта:

Private Sub fntAny_FontChanged(ByVal _
    PropertyName As String)
    Select Case PropertyName
        Case "Name"
            'Какое-либо действие
        Case "Size"
            'Какое-либо действие
        Case "Italic"
            'Какое-либо действие
        Case "Bold"
            'Какое-либо действие
        Case "Underline"
            'Какое-либо действие
        ' ...
        ' Подобным образом можно расширить
        ' функциональные возможности для
        ' каждого свойства шрифта
    End Select
End Sub

Теперь осталось только установить свойство Font формы или элемента управления как fntAny. Так, если вы хотите отслеживать изменения свойств шрифта формы, напишите следующий код в событии Form_Load:

Set fntAny = Me.Font
' Если вы имеете дело с элементом управления,
' тогда Control.Font

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

Совет 281. Как определить, какой именно объект не может быть создан

При разработке крупного VB-приложения, использующего сотни COM-объектов, вы можете в какой-то момент получить сообщение об ошибке такого вида: "429 can't create object". К сожалению, эта информация не очень-то поможет вам определить, какой именно объект не может быть создан. Чтобы преодолеть подобное ограничение, можно написать функцию на базе функции CreateObject из библиотеки Visual Basic runtime objects and procedures:

Public Function CreateObject(sProgID As String) As Object
    '
    On Error GoTo CreateErr
    ' Вызов функции CreateObject из библиотеки
    ' VB runtime objects and procedures
    Set CreateObject = VBA.CreateObject(sProgID)
    Exit Function

CreateErr:
    ' возвращает сообщение об ошибке вместе
    ' с именем объекта, который не может
    ' быть создан
    Err.Raise Err.Number, "CreateObject Wrapper", _
    Err.Description & ": '" & sProgID & "'"
End Function

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

Совет 282. Используйте возможности Office для проверки правописания в элементе управления RichTextBox

VB позволяет осуществить интеграцию возможностей проверки правописания, имеющихся в Microsoft Word 97/2000, в VB-приложения, сохраняя при этом параметры форматирования внутри элемента управления RichTextBox. Продемонстрируем это на таком примере:

  1. Создайте проект Standard EXE в VB.
  2. Добавьте к панели инструментов элемент управления RichTextBox (команда Project|Components).
  3. Добавьте ссылку к библиотеке Microsoft Word 8.0 Object Library (для Word 2000 — Microsoft Word 9.0 Object Library).
  4. Разместите на форме элементы управления RichTextBox и CommandButton.
  5. Переименуйте компонент RichTextBox в rtfText.
  6. Установите свойство Caption командной кнопки как "Правописание".
  7. Введите в событие Click командной кнопки следующий код:

    Private Sub Command1_Click()
      On Error GoTo SpellCheckErr
      Dim oWord As Object
    
      Set oWord = CreateObject("Word.Application")
      '
      ' сохраняет содержимое компонента RichTextBox
      'во временном файле
      rtfText.SaveFile "c:\Vb-db\Test.RTF"
      '
      ' открывает сохраненный файл и проводит
      ' в нем проверку правописания
      oWord.Documents.Open ("c:\Vb-db\Test.RTF")
      oWord.ActiveDocument.SpellingChecked = False
      oWord.Options.IgnoreUppercase = False
      oWord.ActiveDocument.CheckSpelling
      '
      ' сохраняет изменения в RTF-файле
      ' и закрывает его
      oWord.ActiveDocument.Save
      oWord.ActiveDocument.Close
      oWord.Quit
      '
      ' загружает изменения в элемент
      ' управления RichTextBox
      rtfText.LoadFile "c:\Vb-db\Test.RTF"
    
      Set oWord = Nothing
      Screen.MousePointer = vbDefault
      MsgBox "Проверка правописания закончена", _
              vbInformation, "Правописание"
      Exit Sub
      '
    SpellCheckErr:
       MsgBox Err.Description, vbCritical, "Правописание"
       Set oWord = Nothing
    End Sub
    
  8. Сохраните проект и запустите его на выполнение.
  9. Введите какой-нибудь текст в элемент управления RichTextBox и затем щелкните кнопку "Правописание". Вы попадете в среду Word, где и осуществите проверку правописания.

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

Совет 283. Используйте библиотеку ADOX, чтобы установить существование внутренних объектов баз данных

Вскоре после выпуска библиотеки ADO компания Microsoft создала для нее расширение, называемое ActiveX Data Objects Extensions, или просто ADOX, и включающее большую часть функций DAO, которые не вошли в состав стандартной версии ADO. С помощью библиотеки ADOX можно легко определить, входит ли конкретная таблица, разрез данных или запрос в базу данных.

Для этого установите ссылку к библиотеке Microsoft ADO Ext. for DDL and Security. В упрощенном виде ADOX состоит из объекта Catalog, который включает коллекции объектов, описывающих базу данных и содержащиеся в ней таблицы и разрезы данных. Чтобы проверить наличие конкретной таблицы в коллекции Tables, можно пройти шаг за шагом по всей коллекции и сравнить имя каждого элемента с заданной строкой либо воспользоваться предлагаемым ниже способом:

Private Sub Command1_Click()
    Dim con As New ADODB.Connection
    Dim cat As New ADOX.Catalog
    Dim tbl As ADOX.Table
    Dim tblName As String
    con.Open "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=c:\Vb-db\Biblio.mdb;"
    Set cat.ActiveConnection = con
    On Error Resume Next
    tblName = "Titles"
    Set tbl = cat.Tables(tblName)
    If tbl Is Nothing Then
        MsgBox "Таблица " & tblName & " не существует"
    Else
        MsgBox "Таблица " & tblName & " существует"
        Set tbl = Nothing
    End If
    Set cat = Nothing
    Set con = Nothing
End Sub

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

Совет 284. Используйте оператор SendKeys

В нашем Совете 264 мы рассказывали о том, как программным образом имитировать нажатие клавиши с помощью обращения к соответствующей API-функции. Оказывается (каемся — не знали), что в VB можно использовать для этих целей встроенный оператор SendKeys. Например, посылка кода Enter выглядит следующим образом:

SendKeys "{Enter}"

Такая программная имитация нажатия клавиши может быть полезна при создании макрокоманд в MS Office. Ведь некоторые команды в ходе их выполнения выдают диагностические сообщения с требованием подтвердить выполнение операции или выбрать нужный вариант ее реализации. Использование SendKeys позволяет автоматически выполнить подобные "нажатия" кнопок.

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