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

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

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

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


Совет 145. Определение положения указателя линейки прокрутки

Как известно, положение указателя (движка) линейки прокрутки характеризуется значением свойства Value, которое изменяется от значения Min до Max (свойства элемента управления, которые можно задать в окне Properties).

Сделайте такой небольшой опыт. Поместите на форме поле текста, а справа от него — вертикальную линейку прокрутки. Установите Min и Max равными 1 и 10 соответственно (по умолчанию они были 0 и 32767). В событие VScroll1_Change введите код:

Text1.Text = VScroll1.Value

Теперь нажмите F5 для запуска проекта. Прокручивая линейку, вы будете видеть в поле текста цифровое значение положения указателя. Обратите внимание, что свойства LargeChange и SmallChange элемента VScroll задают соответственно величину, на которую будет изменяться значение свойства Value данного элемента управления при щелчке пользователем области между движком и стрелкой прокрутки или при щелчке самой стрелки прокрутки.

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

Text1.Text = VScroll1.Max - VScroll1.Value + VScroll1.Max

Однако еще проще сделать так: свойства Max и Min установите равными отрицательным значениям, например -1 и -10, а в событие Change запишите строку:

Text1.Text = Abs(VScroll1.Value)

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

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

Совет 146. Создание числового счетчика

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

Как легко догадаться, на самом деле это два стоящих рядом элемента управления Text и VScroll с одинаковыми значениями свойств Top и Height, причем последний подобран таким образом, что указатель линейки прокрутки стал невидимым. Теперь, щелкая стрелки линейки, мы увидим автоматическое изменение числового значения с нужным нам шагом (SmallChange). Для практического использования такого счетчика следует также установить его начальное значение, например, при загрузке формы:

Sub Form_Load()
  VScroll1.Value = VScroll1.Max
  Call VScroll1_Change()
End Sub

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

Совет 147. Как измерить протяженность текста

Порой бывает полезным узнать физическую длину текстовой строки (размер в единицах длины с учетом, в частности, размера и типа шрифта) при выводе ее на экран. В принципе для этого можно воспользоваться функциями Win API, но есть еще более простой способ - использовать свойство AutoSize элемента управления Label. Для начала поместите метку (labMeasure) на форму и установите ее свойство AutoSize как True, а свойство Visible - как False. То есть ширина метки будет автоматически устанавливаться равной длине строки, но при этом сам текст метки будет невидимым. После этого напишите такую функцию:

Private Function TextExtent(txt$ As String) As Integer 
  labMeasure.Caption = txt$
  TextExtent = labMeasure.Width
End Function

Обратите внимание, что возвращаемая величина (TextExtent) измеряется в твипах. Твип (twip) - это независимая от экрана единица измерения, используемая для обеспечения того, чтобы местонахождение и пропорции элементов экрана в приложении всегда были одинаковы на всех типах дисплеев. Твип - единица измерения экрана, равная 1/20 точки принтера. В 1 логическом сантиметре содержится приблизительно 567 твипов, а в 1 логическом дюйме - 1440 твипов. Логический сантиметр (дюйм) - это длина элемента экрана, равная 1 сантиметру (дюйму) при печати.

Теперь, если вы захотите выяснить протяженность какого-либо текста, просто вызовите данную функцию с текстовой строкой в качестве параметра. Проиллюстрируем это на следующем примере. Поместим на форму еще один элемент управления - поле текста. Мы хотим выяснить, какую он должен иметь ширину, чтобы целиком вмещать строковую переменную a$ (у текстового поля нет свойства AutoSize). Обратите внимание, что элементы управления Label и TextBox содержат разное количество символов и при равной ширине позволяют записывать различное число видимых символов. Поэтому мы добавляем два пробела к строке A$:

Private Sub Form_Load() 
  txt1$ = "Исходный текст" 
  Text1.Width = TextExtent(" " & txt1$) 
  Text1.Text = txt1$
End Sub

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

Совет 148. Используйте пользовательские диалоговые окна при редактировании ячеек элемента управления DBGrid

Существует простой способ добавления различных пользовательских диалоговых окон ко всем ячейкам элемента управления DBGrid. Для этого вначале поместите на форму компоненты DBGrid и Data. Установите свойства DatabaseName и RecordSource элемента управления Data как, например, biblio.mdb и Publishers. Затем установите свойство DataSource элемента управления DBGrid как Data1 (компонент Data).

Теперь напишите следующий код:

Dim strDBGridCell As String
Private Sub DBGrid1_AfterColEdit(ByVal ColIndex As Integer)
'
  DBGrid1.Columns(ColIndex) = strDBGridCell
End Sub

Private Sub DBGrid1_BeforeColEdit(ByVal _
  ColIndex As Integer, ByVal KeyAscii As Integer, _
  Cancel As Integer)
'
  strDBGridCell = InputBox("Edit DBGrid Cell:", , _ 
  DBGrid1.Columns(ColIndex))
End Sub

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

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

Совет 149. При формировании констант типа Long используйте суффикс &

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

Например, если выполнить такой фрагмент программы:

Code& = &H80040113
Result& = Code& And &HFFFF

то содержимое Result& будет равно не &H0113, как хотелось бы, а &H80040113.

Суть проблемы заключается в том, что переменные Integer и Long являются переменными со ЗНАКОМ, и нужно быть очень внимательным при переходе от беззнакового представления числа (&H...) к знаковому (цифровому десятичному). Рассмотрим такую конструкцию:

Const Mask& = &HFFFF
Print HEX$(Mask&)    ' будет напечатано &HFFFFFFFF !!!!

Дело в том, что константа &HFFFF сначала автоматически представляется в виде переменной Integer и в числовом выражении равна -1. А уже затем при присвоении Mask& = &HFFFF происходит преобразование из Integer в Long и переменная Mask& = -1 (&HFFFFFFFF)! Аналогичные преобразования происходят и в приведенной выше операции Result& = Code& And &HFFFF.

Тогда мы посоветовали читателям для определения констант типа Long, в том числе в арифметических или логических операциях, в диапазоне значений &H8000- &HFFFF (32768-65535) не использовать беззнаковое шестнадцатеричное представление, применяя только десятичное.

Так вот, мы были в общем-то не правы. На самом деле имеется возможность задания "чистых" констант типа Long независимо от диапазона их значений. Действительно, константа &HFFFF преобразуется в целочисленную переменную типа Integer. Но если справа к ней добавить еще один символ &, то сразу получится переменная Long. Попробуйте сами:

Const Mask& = &HFFFF&
Print HEX$(Mask&)

будет напечатано &HFFFF - то, что нам и нужно было получить. Причем машинный код этого фрагмента стал компактнее из-за отсутствия дополнительных внутренних преобразований данных.

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

Совет 150. Использование параметра Alias при работе с функциями API

Ряд функций Windows API содержит параметры, которые могут определяться различным образом. Например, при вызове функции WinHelp последний параметр может передавать данные типа Long или String в зависимости от заданного действия.

Visual Basic позволяет объявлять такой тип данных, как Any, при вызовах функций API, но это может привести к ошибкам несовпадения типов или даже к аварийному отказу системы, если значение имеет неправильный тип.

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

Для иллюстрации данной методики добавьте к модулю формы следующие функции API и константы. Обратите внимание, что два описания функции API отличаются друг от друга только своим названием (WinHelp и WinHelpSearch) и объявлением типа последнего параметра (dwData As Long и dwData As String).

' Объявления API-функции WinHelp
Private Declare Function WinHelp Lib "user32" Alias _
  "WinHelpA" (ByVal hwnd As Long, ByVal lpHelpFile _
  As String, ByVal wCommand As Long, ByVal dwData _
  As Long) As Long
'
Private Declare Function WinHelpSearch Lib "user32" _
  Alias "WinHelpA" (ByVal hwnd As Long, ByVal _ 
  lpHelpFile As String, ByVal wCommand As Long, _
  ByVal dwData As String) As Long
'
Private Const HELP_PARTIALKEY = &H105&
Private Const HELP_HELPONHELP = &H4&
Private Const HelpFile = _
"c:\program files\devstudio\vb5\help\vb5.hlp"

Примечание. Мы учли наш предыдущий совет, указав суффикс & при определении констант. В данном случае (для данных конкретных значений) это было необязательно. Но, во-первых, мы подчеркнули, что формируются константы именно Long. А во-вторых, машинный код стал короче из-за отсутствия дополнительных преобразований.

Теперь поместите на форму две командные кнопки (cmdHelpAbout и cmdHelpSearch) и напишите для них следующий код. Измените путь к файлу Справки в соответствии со своими установками для Visual Basic.

Private Sub cmdHelpAbout_Click()
'
  WinHelp Me.hwnd, HelpFile, HELP_HELPONHELP, &H0
End Sub

Private Sub cmdHelpSearch_Click()
'
  WinHelpSearch Me.hwnd, HelpFile, HELP_PARTIALKEY, "option"
End Sub

Запустите полученный проект на выполнение (F5). Если вы щелкните кнопку cmdHelpAbout, то увидите раздел Справки об использовании справочной системы. Если же вы щелкните кнопку cmdHelpSearch, то на экране появится список элементов Справки по теме option.

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

Совет 151. Как дать возможность пользователям отменить выгрузку форм

Существует множество различных способов выгрузить форму: щелкнуть кнопку Exit или соответствующую команду меню, щелкнуть кнопку со знаком X в верхнем правом углу формы, выбрать команду Close из всплывающего меню окна формы в верхнем левом углу. Можно даже произвести отмену выполнения программы из менеджера задач или перезагрузить компьютер.

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

Private Sub Form_QueryUnload(Cancel As Integer, _
  UnloadMode As Integer)
  '
  ' универсальная проверка выгрузки формы
  Dim strQuestion As String
  Dim intAnswer As Integer
  Dim aryMode As Variant
  '
  aryMode = Array("vbFormControlMenu", _
    "vbFormCode", "vbAppWindows", _
    "vbAppTaskManager", "vbFormMDIForm")
  strQuestion = "Вы готовы выгрузить эту форму?"
  intAnswer = MsgBox(strQuestion, _
   vbQuestion + vbYesNo, aryMode(UnloadMode))
  If intAnswer = vbNo Then Cancel = -1
End Sub 

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

Совет 152. Создание "автоматического" поля текста

С помощью приведенной здесь программы вы можете создать поле текста (TextBox), аналогичное тем, которые реализованы в последних версиях Microsoft Excel или Internet Explorer. Иными словами, каждый раз, когда вы будете вводить какой-либо текст в это поле, первые буквы строки будут сравниваться с элементами невидимого списка, а затем программа сама определит, как следует дописать данную строку, и сделает это за вас.

Для реализации такого поля текста вначале добавьте окно списка к своей форме и установите его свойство Visible как False. В событии Form_Load заполним это окно списка некоторыми элементами. В реальном приложении добавление новых элементов списка следует осуществлять каждый раз, когда пользователь закончит ввод строки. Поместите на форму элемент управления TextBox и введите следующий код:

Option Explicit
 #If Win32 Then  ' 32-разрядная версия VB
   Private Const LB_FINDSTRING = &H18F
   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
 #Else  ' 16-разрядная версия VB
   Private Const WM_USER = &H400
   Private Const LB_FINDSTRING = (WM_USER + 16)
   Private Declare Function SendMessage Lib _
     "User" (ByVal hWnd As Integer, ByVal wMsg _
     As Integer,  ByVal wParam As Integer, lParam _
     As Any) As Long
 #End If

Private Sub Form_Load()
  List1.AddItem "Апельсин"
  List1.AddItem "Банан"
  List1.AddItem "Яблоко"
  List1.AddItem "Персик"
  List1.AddItem "Ананас"
  List1.AddItem "Авокадо"
End Sub

Private Sub Text1_Change()
  Dim pos As Long
  List1.ListIndex = SendMessage(List1.hWnd, _
    LB_FINDSTRING, -1, ByVal CStr(Text1.Text))
  If List1.ListIndex = -1 Then
    pos = Text1.SelStart
  Else
    pos = Text1.SelStart
    Text1.Text = List1
    Text1.SelStart = pos
    Text1.SelLength = Len(Text1.Text) - pos End If
End Sub

Private Sub Text1_KeyDown(KeyCode As Integer, Shift As Integer)
  '
  On Error Resume Next
  If KeyCode = 8 Then ' Backspace
    If Text1.SelLength <> 0 Then
      Text1.Text = Mid$(Text1, 1, Text1.SelStart - 1)
      KeyCode = 0
    End If
  ElseIf KeyCode = 46 Then ' Del
     If Text1.SelLength <> 0 And _
       Text1.SelStart <> 0 Then
       KeyCode = 0
    End If
  End If
End Sub

Теперь запустите созданный вами проект (F5) и попробуйте ввести какой-либо текст.

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

Совет 153. Управление кнопками Minimize/Maximize на MDI-форме

В отличие от других форм MDI-формы не имеют свойств MinButton и MaxButton, с помощью которых можно включать или отключать кнопки Minimize и Maximize на форме. Если добавить следующий код к событию Load родительской MDI-формы, то он отключит обе эти кнопки. Если же вы хотите отключить только одну из них, то поставьте знак комментария у соответствующей строки:

Private Sub MDIForm_Load()
  Dim lWnd As Long
  lWnd = GetWindowLong(Me.hWnd, GWL_STYLE)
  lWnd = lWnd And Not (WS_MINIMIZEBOX)
  lWnd = lWnd And Not (WS_MAXIMIZEBOX)
  lWnd = SetWindowLong(Me.hWnd, GWL_STYLE, lWnd)
End Sub

Затем введите следующий код (который включает объявления необходимых API- функций) в текст самого BAS-модуля:

Option Explicit
 ' описание API-функций и констант
 #If Win32 Then
   Private Declare Function SetWindowLong Lib _
     "user32" Alias "SetWindowLongA" (ByVal _
     hWnd As Long, ByVal nIndex As Long, ByVal _
     dwNewLong As Long) As Long
   Private Declare Function GetWindowLong Lib _
     "user32" Alias "GetWindowLongA" (ByVal _
    hWnd As Long, ByVal nIndex As Long) As Long
 #Else
   Declare Function SetWindowLong Lib "User" _
     (ByVal hWnd As Integer, ByVal nIndex As _
     Integer, ByVal dwNewLong As Long) As Long
   Declare Function GetWindowLong Lib "User" _
     (ByVal hWnd As Integer, ByVal nIndex As _
     Integer) As Long
 #End If 

Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const GWL_STYLE = (-16)

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

Совет 154. Как сделать видимыми кнопки Minimize/Maximize

В некоторых случаях бывает необходимо задать свойство BorderStyle какой-либо формы как Fixed Dialog, и тогда VB не выводит кнопки Minimize и Maximize в поле заголовка формы. Теперь, если задать для этой же формы свойства MinButton и MaxButton как True, команды Minimize и Maximize в контекстном меню формы станут видимыми. Однако сами кнопки на форме останутся по-прежнему невидимыми. Чтобы исправить эту ошибку, введите описание API-функций и констант из предыдущего совета и запишите следующий код в стандартный модуль:

Public Sub SetCaptionButtons(Frm As Form)
  Dim lRet As Long
  lRet = GetWindowLong(Frm.hWnd, GWL_STYLE)
  SetWindowLong Frm.hWnd, GWL_STYLE, lRet Or _
  WS_MINIMIZEBOX * (Abs(Frm.MinButton)) Or _
  WS_MAXIMIZEBOX * (Abs(Frm.MaxButton))
End Sub

Теперь следует вызвать подпрограмму SetCaptionButtons из события Form_Load, передав ссылку на вашу форму.

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