Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© 2000, Андрей Колесов, Ольга ПавловаЧасто при заполнении элемента управления 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
Запустите проект на выполнение — в окне списка выводятся только имена сотрудников. Щелкните любой из элементов, и на экране появится окно сообщения, содержащее имя выбранного сотрудника и его идентификатор.
Как известно, свойство 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
Для того чтобы быстро определить, не забыл ли пользователь вставить компакт-диск в дисковод 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
В некоторых случаях вам может захотеться добавить дополнительный элемент к системному меню формы. (Кнопка системного меню находится в верхнем левом углу формы.) Для этого воспользуйтесь тремя 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.
Если вы хотите определить, находится ли в списке какой-либо определенный элемент, обычно вы создаете массив, в котором и осуществляете поиск. В качестве более быстрой альтернативы для коротких списков предлагаем использовать строку неограниченной длины вместо массива. Например, предположим, что у вас есть элемент управления 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
При создании строки соединения (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.
Элемент управления 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, а затем заново заполнит последний из них обновленными данными.
В зависимости от версии 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.
Во многих случаях может возникнуть необходимость сделать ссылку на файл в соответствии с соглашением об именах файлов 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
Если вы когда-либо работали с 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 не будет содержать имя временного файла.
Порой бывает нужно вывести числа с фиксированным количеством цифр, когда спереди добавляются нули. Например, если требуется представить число 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
Именно так назвался один из первых советов, опубликованных в самом начале цикла "Советы для тех, кто программирует на 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).