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

Советы тем, кто программирует на Visual Basic

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

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


Совет 234. Используйте свойство ItemData элемента управления ListBox для хранения идентификаторов

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

Private Sub Form_Load()
  ' Заполнение списка и массива ItemData
  ' соответствующими элементами
  With List1
    .AddItem "Владимир Николаев"
    .ItemData(List1.NewIndex) = 42310
    .AddItem "Максим Сергеев"
    .ItemData(List1.NewIndex) = 52855
    .AddItem "Михаил Смирнов"
    .ItemData(List1.NewIndex) = 64932
    .AddItem "Анна Васильева"
    .ItemData(List1.NewIndex) = 39227
  End With
End Sub

Private Sub List1_Click()
  Dim Msg As String
  ' Вывод окна сообщения, содержащего
  ' идентификатор и имя сотрудника
  With List1
    Msg = .ItemData(.ListIndex) & " " & .List(.ListIndex)
    MsgBox Msg
  End With
End Sub

Запустите проект на выполнение — в окне списка выводятся только имена сотрудников. Щелкните любой из элементов, и на экране появится окно сообщения, содержащее имя выбранного сотрудника и его идентификатор.

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

Совет 235. Как правильно сгенерировать значения ADO RecordCount в VB

Как известно, свойство ADO RecordCount возвращает количество записей в наборе записей ADO. Однако в некоторых случаях данное свойство возвращает значение, равное -1. Почему так происходит? Дело в том, что значение, возвращаемое свойством RecordCount, зависит от типа курсора в наборе записей: -1 для курсора, перемещаемого только вперед; реальное количество записей — для статического курсора или курсора, управляемого клавишами на клавиатуре; -1 или реальное количество записей — для динамического курсора в зависимости от источника данных.

Однако вам будет также интересно узнать, что значение RecordCount равно -1 для наборов записей, созданных с помощью метода Execute для объекта Connection или Command. Это происходит потому, что данный метод генерирует набор записей, перемещаться по которому можно только вперед и который возвращает, как мы уже упоминали выше, -1. В качестве примера создайте следующую подпрограмму внутри стандартного VB-проекта. (Не забудьте установить ссылку на Microsoft ActiveX Data Objects 2.1 Library и изменить путь к базе данных Biblio.mdb в соответствии с тем, где она хранится на вашем компьютере.) Запустите проект на выполнение и вы увидите окно сообщения, содержащее такие значения: -1 для набора записей на базе myConRst и 6246 — для myKeyRst.

Sub TestRecordCount()
  Dim myConn As ADODB.Connection
  Dim myComm As String
  Dim myConRst As ADODB.Recordset
  Dim myKeyRst As ADODB.Recordset
  Dim sConnection As String
  sConnection = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
          "Data Source=D:\Microsoft Visual Studio\VB98\Biblio.mdb"

  Set myConn = New ADODB.Connection
  Set myKeyRst = New ADODB.Recordset

  myConn.Open sConnection
  myComm = "Select * From Authors"
  Set myConRst = myConn.Execute(myComm, , adCmdText)

  myKeyRst.Open myComm, myConn, adOpenKeyset
  MsgBox "RecCount from Connection: " & myConRst.RecordCount & _
          vbCr & "From Recordset: " & myKeyRst.RecordCount

  Set myKeyRst = Nothing
  Set myConRst = Nothing
  Set myConn = Nothing

End Sub

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

Совет 236. Пусть VB сам определяет, есть ли в дисководе CD-ROM компакт-диск или нет

Для того чтобы быстро определить, не забыл ли пользователь вставить компакт-диск в дисковод CD-ROM, используйте свойство IsReady для объекта Drive. Данное свойство возвращает значение True (Истина) только в том случае, если компакт-диск находится в устройстве CD-ROM. Рассмотрим следующий пример. Установите ссылку к библиотеке Microsoft Scripting Runtime (scrrun.dll), а затем создайте переменную Drive для дисковода CD-ROM. Протестируйте свойство IsReady, как показано ниже:

Dim FSO As FileSystemObject
Dim CDDrive As Drive

Set FSO = New FileSystemObject
Set CDDrive = FSO.GetDrive("E:")
If CDDrive.IsReady Then
  MsgBox CDDrive.VolumeName
Else
  MsgBox "Вставьте, пожалуйста, компакт-диск"
End If

Set CDDrive = Nothing
Set FSO = Nothing

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

Совет 237. Как добавить новый элемент к системному меню формы

В некоторых случаях вам может захотеться добавить дополнительный элемент к системному меню формы. (Кнопка системного меню находится в верхнем левом углу формы.) Для этого воспользуйтесь тремя API-функциями — GetSystemMenu, AppendItemMenu и DrawMenuBar:

Option Explicit
Private Declare Function DrawMenuBar Lib "user32" _
  (ByVal hWnd As Long) As Long

Private Declare Function GetSystemMenu Lib "user32" _
  (ByVal hWnd As Long, ByVal bRevert As Long) As Long

Private Declare Function AppendMenu Lib "user32" Alias _
  "AppendMenuA" (ByVal hMenu As Long, ByVal wFlags _
  As Long, ByVal wIDNewItem As Long, ByVal lpNewItem _
  As Any) As Long

Const MF_STRING = &H0&

Private Sub Form_Load()
  Dim SysMenu As Long
  SysMenu = GetSystemMenu(Me.hWnd, False)
  If SysMenu Then
    AppendMenu SysMenu, MF_STRING, 0, "Свой элемент меню"
    DrawMenuBar Me.hWnd
  End If
End Sub

Как видно из приведенного выше кода, подпрограмма находит указатель к копии системного меню текущей формы. Затем она добавляет строку "Свой элемент меню" к списку команд системного меню, и наконец, перерисовывает меню, добавляя новый текст в конец списка.

Команду DrawMenuBar следует использовать всегда при изменении системного меню, даже если в данный момент оно является невидимым.

То, что вы просто добавили новую команду к системному меню, совершенно не означает, что она будет выполнять какую-либо операцию: для этого необходимо написать соответствующий код. Функция AppendMenu предлагает несколько различных параметров, описывающих элемент, который вы хотите добавить к системному меню. Полный список этих параметров приводится в файле Справки.

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

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

Совет 238. Как быстро осуществить проверку на наличие определенного элемента в списке

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

For X = 0 To List1.ListCount - 1
  strItemList = strItemList & "[" & List1.List(X) & "]"
Next X

После этого с помощью функции Instr() определите, содержит ли новая строка искомый элемент:

If InStr(strItemList, "[" & strTestItem & "]") Then
  MsgBox "Уже есть в списке"
Else
  MsgBox "Добавьте в список..."
End If

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

Совет 239. Используйте для объектов ADO родные драйверы OLEDB вместо драйверов ODBC

При создании строки соединения (connection string) для объектов ADO имеется возможность задать драйвер источника данных либо как Driver, например так:

Driver={SQL Server};DBQ=database_file

либо как Provider, наподобие следующего:

Provider=Microsoft.Jet.OLEDB.4.0;Data Source=database_name

Однако в первом случае объект ADO использует более старые драйверы ODBC для связи с источником данных, в то время как во втором — применяется OLEDB, являющийся родным для ADO интерфейсом доступа к данным. Поэтому всегда, когда это возможно, пользуйтесь вариантом Provider. Напоминаем, что родные драйверы OLEDB существуют для SQL Server, Index Server, Site Server Search и Oracle.

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

Совет 240. Как модифицировать элемент управления DataGrid при помощи изменения набора записей

Элемент управления DataGrid, входящий в состав VB 6.0, является хорошим средством для представления данных в табличном виде. Однако в нем имеется несколько ошибок, часть из которых была исправлена в Service Patch 3, но некоторые из них все же остались. Например, если вы свяжете элементы управления DataGrid и DataEnvironment, затем измените набор данных, используемый в DataEnvironment, и обновите DataGrid с помощью метода Refresh, этот компонент останется неизменным. К сожалению, метод Refresh не работает, когда свойство DataSource элемента управления DataGrid установлено как DataEnvironment. Поэтому, чтобы отразить изменения, которые были сделаны в наборе данных элемента управления DataEnvironment, следует сначала обновить эти данные, а затем повторно связать DataGrid с DataEnvironment. Поэтому, если у вас есть кнопка Refresh, событие Click для нее может выглядеть так:

DataEnvironment1.rsCommand1.Requery
Set DataGrid1.DataSource = DataEnvironment1

Теперь, когда вы щелкните кнопку Refresh, приведенный выше код повторно свяжет элементы управления DataEnvironment и DataGrid, а затем заново заполнит последний из них обновленными данными.

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

Совет 241. Как запустить на выполнение VB-приложение в процессе инициализации Windows

В зависимости от версии Windows, в которой вы работаете, существуют два различных способа выполнить VB-приложение в процессе загрузки системы. Для Windows 9x поместите команду Shell в раздел [Boot] файла System.ini, например так:

Shell=Myprog.exe

Для Windows NT/2000 используйте ту же самую команду Shell в Системном Реестре (Registry) в разделе

HKEY_CURRENT_USER\Software\Microsoft\WindowsNT\CurrentVersion\Winlogon

Будьте, однако, осторожны, выполняя подобные модификации, поскольку это может привести к некорректной работе Windows.

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

Совет 242. Как восстановить короткое имя файла в VB без помощи API-функций

Во многих случаях может возникнуть необходимость сделать ссылку на файл в соответствии с соглашением об именах файлов 8.3. Вы, несомненно, неоднократно встречали такие имена в MS-DOS. Например, используя данное соглашение, папка Program Files превращается в Progra~1. Поэтому вам приятно будет узнать о том, что в VB 6.0 появилась возможность восстанавливать короткие имена файлов, не обращаясь к API-функции GetShortPathName. В качестве альтернативы новая библиотека Scripting Runtime предлагает свойство ShortPath для объектов File и Folder. Чтобы получить короткое имя файла, просто добавьте ссылку к Microsoft Scripting Runtime, а затем введите следующий код:

Private Sub Form_Load()
  Dim fsoFile As File, fso As FileSystemObject
  Set fso = New FileSystemObject
  Set fsoFile = fso.GetFile("C:\MyReallyLongName.txt")
  MsgBox fsoFile.ShortPath
  Set fsoFile = Nothing
  Set fso = Nothing
End Sub

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

Совет 243. Создавайте временные VB-файлы с помощью API-функции

Если вы когда-либо работали с Word или другим приложением Office, вы, вероятно, обратили внимание, что каждый раз при открытии файла Office создает временный файл, в котором будут храниться все изменения. И тогда у вас мог возникнуть вопрос, как создавать произвольные временные файлы в своем собственном VB-приложении. Для этого воспользуйтесь API-функцией GetTempFileName:

Public Declare Function GetTempFileName Lib "kernel32" _
  Alias "GetTempFileNameA" (ByVal lpszPath As String, _
  ByVal lpPrefixString As String, ByVal wUnique As Long, _
  ByVal lpTempFileName As String) As Long

Аргумент lpszPath передает полное имя файла (включая путь к нему), lpPrefixString позволяет добавить префикс из трех букв слева от имени файла, а wUnique дает команду Windows либо создать файл с произвольным именем (wUnique равен 0), либо использовать заданный номер. Параметр lpTempFileName, конечно же, содержит имя нового временного файла. В качестве примера поместите приведенное выше объявление API-функции в стандартный модуль, а затем напишите следующую функцию:

Private Function GenTempName(sPath As String)
  Dim sPrefix As String
  Dim lUnique As Long
  Dim sTempFileName As String

  If IsEmpty(sPath) Then sPath = "D:\Articles\IVB"
  sPrefix = "fVB"
  lUnique = 0

  sTempFileName = Space$(100)
  GetTempFileName sPath, sPrefix, lUnique, sTempFileName
  sTempFileName = Mid$(sTempFileName, 1, _
    InStr(sTempFileName, Chr$(0)) - 1)
  GenTempName = sTempFileName
End Function

После этого откройте новую форму и введите следующий код в ее событие Click. (Замените D:\Articles\IVB на любой допустимый путь.)

MsgBox GenTempName("D:\Articles\IVB")

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

Обратите внимание, что для того чтобы приведенная выше функция работала надлежащим образом, вы должны задать только допустимый путь. В противном случае функция GetTempFileName вернет 0 и нулевой параметр в качестве имени файла в Windows NT. В Windows 9x неправильно указанный путь также вернет 0, а параметр lpTempFileName не будет содержать имя временного файла.

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

Совет 244. Как преобразовать число в строку с фиксированным количеством цифр

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

Public Function FixNumber$(SourceNumber, Lend%)
  '
  ' Преобразование числа в строку с фиксированным
  ' количеством цифр (добавляем нули спереди)
  ' =======================================
  ' SourceNumber — исходное число
  ' Lend — количество цифр
  '
  Dim MaxNumber
  MaxNumber = 10 ^ Lend
  If SourceNumber >= MaxNumber Then ' превышен предел
    ' тут могут быть разные варианты
    FixNumber$ = SourceNumber      ' вывод числа полностью
    ' или
    ' FixNumber$ = String$(Lend, "?")  ' замена на знаки вопроса
  Else ' укладывается в рамки
    FixNumber$ = Right$(MaxNumber + SourceNumber, Lend)
  End If
End Function

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

Совет 245. Используйте режим Option Explicit

Именно так назвался один из первых советов, опубликованных в самом начале цикла "Советы для тех, кто программирует на VB" четыре года назад ("КомпьютерПресс" N 5/96). С тех пор мы неоднократно показывали на разных примерах пользу обязательного объявления переменных: время на написание нескольких операторов Dim многократно окупается сокращением затрат на отладку. И тем не менее, вернуться к этому вопросу вновь нас заставило письмо читателя Игоря, который обратился к нам с "неразрешимой" проблемой.

Игорь хотел подключить DLL-процедуру, написанную на Фортране, к VB. Вопрос корректной передачи параметров в DLL не совсем тривиален, поэтому мы посоветовали ему начать с отладки какого-нибудь простого случая, например, с передачи имени файла, который нужно прочитать в DLL, в виде строковой переменной.

С внешней стороны тестовый пример выглядел правильно:

Обращение к DLL из VB:

Private Declare Sub OpenFileFortran _
  Lib "FortLib" (ByVal ss As String)
  '
  Dim cc As String
  сc = "sams.txt"
  Call OpenFileFortran(cc)
End Sub

Процедура на Фортране:

Subroutine OpenFileFortran (pfn)
  character*(*) pfn
  open(0,file=pfn)
  close(0)
End

Прислав этот пример, Игорь сообщил, что он уже два дня безуспешно сражается с ним, причем обычно происходит аварийное завершение программы с "мгновенным исчезновением среды VB". Он испробовал разные варианты передачи имени файла, в том числе с использованием байтовых массивов и структур данных, но с тем же неудачным результатом. Распечатка переменной pfn упорно показывала отсутствие нужного имени файла. А может быть, тут проблема в путанице ANSI/Unicode? Или "глючит" Windows 98 Second Edition?

Однако "ларчик просто открывался". На самом деле в VB-программе Игоря используются две разные переменные: в первой и третьей строках указана переменная "cc" (обе буквы латинские), а во второй — "сc" (первая буква русская).

Определить это на взгляд просто невозможно, но мы всегда применяем режим Option Explicit — поэтому компилятор сразу же сообщил, что во второй строке используется неопределенная переменная. Мы, конечно, сначала не поверили своим глазам, но потом вспомнили ситуацию из времен DOS, когда у нас был установлен какой-то русификатор клавиатуры, у которого переключение клавиш на русский язык порой срабатывало только после нажатия первой клавиши. В результате при вводе такого текста

Andy спит

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

После исправления идентификатора пример стал нормально работать. Таким образом, для того чтобы не терять несколько дней на отладку, и нужно-то было всего-навсего установить режим Option Explicit (который автоматически определяется командой Tools|Options|Editor|Required Variable Declaration).

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