Visual2000 · Архив статей А.Колесова & О.Павловой
Андрей Колесов, Ольга Павлова
© 1996, Андрей Колесов, Ольга ПавловаЭлемент управления Masked Edit имеет свойство Text, но оно не отображается в окне Properties, то есть начальную установку текста нельзя выполнять в процессе разработки программы. Поэтому такую установку нужно выполнять на программном уровне, например в момент загрузки формы:
Private Sub Form_Load () MaskEdBox1.Text="369-76-97" End Sub
При работе с Masked Edit следует помнить, что строковая переменная должна полностью совпадать с символами в шаблоне ввода (свойство Mask), включая литерные символы и подчеркивание. В приведенном выше примере подразумевалось, что шаблон номера имеет вид "###-##-##".
frmCustomer.Left = frmCustomer.Left + 100 frmCustomer.Top = frmCustomer.Top + 50
Но, используя метод Move, можно сделать то же самое процентов на 40 быстрее:
frmCustomer.Move = frmCustomer.Left + 100, _ frmCustomer.Top + 50
If iNumber Then
будет работать быстрее, чем арифметическая
If iNumber <> 0 Then
При вводе данных в текстовые поля формы бывает не очень удобно пользоваться клавишей Tab. Например, вы привыкли, что ввод данных в текущее поле заканчивается нажатием Enter. Чтобы сделать это, добавьте такой код для нужного элемента управления:
Sub Text1_KeyPress (KeyAscii As Integer) If KeyAscii = 13 Then ' клавиши Enter SendKeys "{Tab}" KeyAscii = 0 End If End Sub
А чтобы реализовать режим AutoTab при вводе данных в тестовое поле, можно использовать проверку свойства MaxLength в процедуре обработки события Change:
Sub Text1_Change () If Len(Text1)=Text1.MaxLength Then SendKeys "{Tab}" End If End Sub
В этом случае переход к следующему полю будет выполняться автоматически после ввода последнего символа.
В программах довольно часто приходится использовать переменные-флажки, которые имеют значения "ноль/не ноль". Конечно, можно написать:
If bPerform Then bPerform = False Else bPerform = True
Но так будет выглядеть симпатичнее:
bPerform = Not bPerform ' состояние флага 0 или -1
А если вы больше привыкли иметь дело с арифметическими операциями, то можно использовать операцию Xor:
iFlag = iFlag Xor iMaskFlag
Последний вариант интересен тем, что в этом случае может использоваться любое ненулевое значение флага, которое в этом случае представлено iMaskFlag (1, -1, 777 и пр.). Еще одно замечание. Будьте внимательны, используя оператор Not при работе с целочисленными переменными, которые могут принимать значения, отличные от -1 (True) и 0 (False):
Sum cmdBool_Click() Dim iBool As Integer, iTemp As Integer iBool = True Print iBool ' печатается -1 Print Not iBool ' печатается 0 iTemp = 5 Print iTemp ' печатается 5 Print Not iTemp ' печатается -6 ' любое ненулевое значение воспринимается как True If iTemp Then Print "iTemp = True" ' печататься будет здесь Else Print "iTemp = False" End If End Sub
Все переменные (в том числе и массивы) в VB могут быть динамическими или статическими. Их принципиальное отличие заключается в том, что резервирование и освобождение динамических переменных осуществляется в процессе выполнения программы по некоторым специальным запросам или автоматически при выполнении определенных операций. Например, при обращении к процедуре автоматически резервируются все ее локальные динамические переменные, а при выходе из нее они так же автоматически освобождаются. Для простых переменных это реализуется в виде некоторого стека, а для массивов — довольно сложным алгоритмом работы с динамической памятью. Но программисту особого дела до этого нет, так как здесь работает непосредственно VB. Хотя иногда при разработке сложных проектов приходится учитывать особенности механизма диспетчеризации (это бывает, когда вдруг появляются неприятные сообщения типа Out of memory).
Статические переменные формируются на этапе компиляции и существуют в процессе работы программы.
Достоинства и недостатки эти типов переменных понятны: динамические переменные позволяют оптимизировать использование памяти — они создаются только на время их реальной необходимости. Но работа с ними требует несколько большего времени для выполнения операций резервирования и освобождения. Хотя последнее замечание — чисто теоретическое: несколько моих попыток отловить на тестах эти временные отличия не привели к сколь-нибудь значимым результатам.
В VB по умолчанию все локальные переменные любой процедуры являются динамическими. Естественно, что в результате этого все значения локальных переменных теряются после выхода из нее. Поэтому, если вы хотите сохранить содержимое переменных между обращениями к процедуре, следует объявить их статическими, заменив ключевое слово Dim на Static. Это особенно полезно при создании постоянных счетчиков, а также в режиме отладки.
Приведем пример, демонстрирующий общее число одиночных щелчков мыши по кнопке (первый раз, когда вы щелкаете кнопку, счетчик начинает счет со значения по умолчанию, равного нулю):
Sub Command1_Click() Static Counter As Integer ' Счет начинается с 0 Counter = Counter + 1 Print Counter End Sub
Обратите внимание, что все переменные, описанные как глобальные на уровне модуля (оператором Dim в разделе Declarations), являются также статическими. Используя их, можно легко создать общие счетчики для нескольких процедур, например так:
' раздел Declarations: Dim Counter As Integer ' Counter - общий счетчик щелчков по двум кнопкам Sub Command1_Click() Counter = Counter + 1 Print Counter End Sub Sub Command2_Click() Counter = Counter + 1 Print Counter End Sub
А если вы хотите, чтобы отсчет начинался с нуля при каждой загрузке формы, можно добавить следующий код:
Sub Form_Load () Counter = 0 End Sub
Для описания глобальных переменных на уровне модуля в VB 4.0 можно использовать оператор Private, а для создания переменных, доступных в любых процедурах всего приложения, — Public.
Динамические массивы представляют особую ценность для программиста. Они позволяют резервировать размеры массива, соответствующие реальным требованиям задачи, меняя их в случае необходимости (здесь есть интересные моменты, связанные с возможностью сохранения данных при перерезервировании массивов). В DOS'овских версиях они также позволяли использовать свободную основную память компьютера (статические массивы вместе с простыми переменными размещались в ближнем сегменте данных 64 Кбайт).
Динамические массивы можно создавать как на уровне процедуры, так и на уровне модуля. В первом случае они создаются и существуют, как и простые локальные переменные процедуры, только на время ее выполнения (с момента выполнения оператора Dim до Exit Sub/Function). Во втором случае они становятся как бы псевдостатическими: хранятся постоянно в памяти, но управление их резервированием (изменением размерности) выполняется в явном виде с помощью операторов ReDim в процедурах данного модуля. Однако при объявлении динамических массивов на уровне модуля есть некоторые нюансы, на которые следует обратить внимание.
Но прежде чем рассмотреть их для VB/Win, имеет смысл вспомнить, как это выглядело для DOS'овских версий (Quick, PDS, Visual). Общими правилами здесь являются следующие:
Рассмотрим такой пример для Basic/DOS:
' Модуль TEST1.BAS ' описание типа и размерности динамического массива, ' глобального на уровне модуля: REDIM SHARED Array(10,20) AS INTEGER SUB TestSub1 PRINT Array(3,3) REDIM Array(8,97) AS INTEGER ' перерезервирование массива END SUB SUB TestSub2 REDIM Array(4,7) AS INTEGER ' начальное создание массива PRINT Array(2,2) END SUB
Оператор REDIM SHARED... в разделе объявлений говорит о том, что определяется целочисленный двухмерный массив, глобальный на уровне модуля (для этого используется слово SHARED). Но фактически никакого массива не создается (имеется в виду, что это не головной модуль программы, с которого начинается ее выполнение). Описание границ массива (10,20) игнорируется и нужно только для того, чтобы показать число его индексов. Поэтому если мы сразу обратимся к процедуре TestSub1, то при выполнении оператора PRINT Array(3,3) будет выдано сообщение об ошибке ь 9 (хотя в нем говорится о нарушении границ, на самом деле массива просто не существует). Для работы с массивом нужно его создать, обратившись, например, вначале к процедуре TestSub2, а уже после этого вызывать TestSub1, в которой в том числе можно изменять его границы.
Обратите внимание, что при работе в среде QB (только в ней!) при определении массива в процедуре можно опустить описание типа AS INTEGER, но массив все равно будет резервироваться целочисленным. Впрочем, компилятор в любом случае выдаст ошибку о несовпадении типов данных и заставит вас устранить эту двусмысленность.
Если в процедуры этого же модуля добавить такие операторы, то они будет сразу определены как ошибочные:
REDIM Array(100) ' Изменение числа индексов массива REDIM Array(4,7) AS SINGLE ' Изменение типа данных DIM Array(4,7) AS INTEGER ' Дубликатное резервирование массива: ' имя локального массива совпадает с ' глобальным на уровне модуля
Теперь посмотрим, как подобная конструкция может выглядеть для VB/Win 4.0:
' описание ТОЛЬКО типа (целочисленного) динамического массива, ' глобального на уровне модуля: Dim Array() As Integer Private Sub Form_Load() Dim Array(10) ' это локальный динамический массив!!! Array(3) = 15.5 Debug.Print Array(3) ' к тому же - Variant ' (будет напечатано 15.5) End Sub Private Sub Command1_Click() Print Array(3) ' будет напечатано 3 ' или появится сообщение об ошибке End Sub Private Sub Command2_Click() ' резервируем двумерный массив ReDim Array(10, 20) Array(2, 2) = 22.3 ' но Integer! Print Array(2, 2) ' будет напечатано 22 ' резервируем одномерный массив ReDim Array(15) Array(3) = 3.3 Print Array(3) ' будет напечатано 3 End Sub Private Sub Command3_Click() ' резервируем трехмерный массив ReDim Array(10, 20, 30) Array(1, 2, 3) = 123.1 Print Array(1, 2, 3) End Sub Private Sub Command4_Click() ' пытаемся изменить тип данных ReDim Array(10, 20) As Single End Sub
Прежде всего следует отметить, что запуск такого приложения в среде VB/Win 4.0 выполняется без проблем — никаких ошибок синтаксиса не обнаружено (Basic/DOS тут покажется чрезмерно строгим). Здесь следует обратить внимание на следующее:
Некоторые выводы
Проблем здесь довольно много, и одна из самых главных — преобразование 16-разрядного интерфейса API в 32-разрядный. В общем случае все обращения к функциям API нужно внимательно переделывать (это в первую очередь относится к операторам Declare). Здесь существуют несколько разных вариантов или их комбинаций:
При этом особое внимание требуется тогда, когда внешний вид обращений остался вроде бы тем же, например целые данные просто превратились из 16-разрядных в 32-разрядные (это относится и к кодам ошибок). Вот как, например, будет выглядеть одновременная реализация процедуры GetWindowRect в Win16 и Win32:
#If Win32 Then Private Declare Function GetWindowRect Lib "user32" _ (ByVal lHwnd As Long, uRect As RectType) As Long Private Type RectType Left As Long Top As Long Right As Long Bottom As Long End Type #Else Declare Sub GetWindowRect Lib "user" _ (ByVal iHwnd As Integer, uRect As RectType) Private Type RectType Left As Integer Top As Integer Right As Integer Bottom As Integer End Type #End If Dim uRect As RectType Private Function lGetWindowRect(iHwnd As Integer, uRect As RectType) #If Win32 Then lHwnd = iHwnd lGetWindowRect = GetWindowRect(lHwnd, uRect) #Else lGetWindowRect = 0 Call GetWindowRect(iHwnd, uRect) #End If End Function
Проблема одновременного сопровождения 16- и 32-разрядного вариантов одного приложения несколько упрощается с появлением в VB 4.0 директив условной компиляции, хотя даже из этого простого примера видно, что хлопот с настройкой кода на работу и в Win16, и в Win32 вполне хватает. В данном случае API-процедура из подпрограммы превратилась в функцию, а первый передаваемый параметр поменял тип с Integer на Long. Еще одно отличие, которое не сразу заметно: поля структуры uRect также стали Long.
Очевидно, что дело здесь сводится не только к замене описания Declare, но и к коррекции кода программы, где выполняется работа с процедурой и ее параметрами.
Что касается определяемых пользователем структур, то следует иметь в виду, что в VB выполняется выравнивание полей. Например, длинное целое будет иметь адрес, кратный 4. Это надо учитывать при работе с библиотеками, использующими иной способ упаковки.
Довольно неприятным моментом является и то, что в Win32 имена функций стали чувствительными к регистру букв. Если в приведенном выше примере в операторе Declare вы напишите имя GetwindowRect, то во время исполнения приложения при обращении к этой функции появится сообщение об ошибке "Specified DLL function not found" (запрошенная DLL-функция не найдена). Но за соответствием идентификаторов в Declare и в последующем коде программы VB следит сам.
В заключение этой темы имеет смысл напомнить, что с помощью ключевого слова Alias в операторе Declare можно менять фактические имена DLL-функций на альтернативные. Это может, например, пригодиться, если имена аналогичных API функций в версиях Win16 и Win32 отличаются.
Разумеется, здесь рассмотрены далеко не все проблемы перехода из Win16 в Win32, в том числе в плане использования функций API. По довольно единодушному мнению различных экспертов, самым надежным и лучшим пособием в этом вопросе является книга известного американского автора и разработчика VB-продуктов Дэна Эпплмана (Daniel Appleman) "Visual Basic Programmer's Guide to the Win32 API" (издательство Macmillan Computer Publishing/Ziff-Davis Press, 1996). Ранее его же книга, посвященная Win16 API, была бестселлером в течение нескольких лет.
Для тех, кто занимается преобразованием программ из VB 3.0 в VB 4.0, можно посоветовать использовать бесплатную копию комплекта программ и документации Upgrade Wizard подразделения Crescent фирмы Progress Software, которую можно найти на Web-странице: http://crescent.progress.com. Там содержится много ценной информации и полезных рекомендаций, а утилита-"мастер" автоматически преобразует значительную часть кода, расставив пометки там, где нужно исправить его вручную. Причем с ее помощью можно получить код как для Win16, так и для Win32.
Когда вы создаете вспомогательные программные модули, предназначенные для использования в разных приложениях, которые могут быть как 16-, так и 32-разрядными, то в них нужно уметь определять тип приложений.
При работе с VBA и Access проверку режима работы приходится выполнять непосредственно в процессе выполнения приложения. Приведенные ниже примеры процедур используются для определения того, является ли конкретное приложение 32-разрядным или нет. Обратите внимание, что свойство Application.OperatingSystem в Microsoft Excel и в Microsoft Project возвращает не установленную версию Windows, а слой Windows, на котором выполняется приложение, например 16-разрядная подсистема в Windows NT.
Function Engine32%() If instr(Application.OperatingSystem, "32") Then Engine32% = True End Function
Function Engine32 If Val(GetSystemInfo$(23)) > 6.3 Or Len(GetSystemInfo$(23)) = 0 _ Then Engine32 = -1 Else Engine32=0 End Function
Function Engine32%() If SysCmd(7) > 2 Then Engine32% = True End Function
Вот пример работы, связанный с "разрядностью":
Declare Function GetTickCount32 Lib "KERNEL32" _ Alias "GetTickCount" () As Long Declare Function GetTickCount16 Lib "USER" Alias _ "GetTickCount" () As Long Function GetTickCount() As Long If Engine32%() Then GetTickCount=GetTickCount32() Else GetTickCount=GetTickCount16() End If End Function
Здесь следует обратить внимание на то, что в Win16 и Win32 используемая API-функция GetTickCount имеет одно и тоже название. Поэтому, когда нет возможности применить механизм условной компиляции, необходимо использовать управляющее слово Alias для того, чтобы изменить имя функции по крайней мере в одном из операторов Declare (в данном примере GetTickCount32 и GetTickCount16). Затем в зависимости от разрядности приложения переменная GetTickCount устанавливается в соответствии с точным именем функции API (GetTickCount32 или GetTickCount16) и вызовом данной функции API.