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

Алексей Малинин, Ольга Павлова

Совет 388. Передача адреса процедуры через структуру

Совет 389. Программная регистрация ActiveX DLL и OCX

Совет 390. Быстрое перемещение между процедурами

Совет 391. Как посчитать число строк в текстовом окне

Совет 392. Как определить позицию курсора в Rich Textbox

Совет 393. Программная имитация нажатия кнопок

Совет 394. Динамическое управление заголовками колонок DataGrid

Совет 395. А вы знаете о свойстве LockControls формы?

Совет 396. Используйте функцию ExtFloodFill для цветной заливки поверхности

Совет 388. Передача адреса процедуры через структуру

Как известно, функция AddressOf позволяет передавать в качестве параметра адрес VB-процедуры при обращении к DLL-процедурам вообще и к Win API в частности. Обычно это нужно для реализации механизма «обратного вызова» (Callback). Однако порой бывает необходимо передавать такой адрес в виде одного из полей структуры данных. Для решения подобной задачи следует иметь в виду, что ключевое слово AddressOf можно использовать и при обращении к VB-процедуре, в связи с чем можно предложить такой вариант программы:

Public Sub Main()  
   Type MySturture  
     lpfnProc As Long  ' адрес процедуры  
   End Type  
   Dim MyStruc  
    ' вычисление адреса процедуры MyProc
   MyStruc.lpfnProc = FcnPtr(AddressOf MyProc)  
End Sub  
Public Function FcnPtr(ByVal Whatever As Long) As Long  
  ' фиксируем значение переданного адреса процедуры
  FcnPtr = Whatever  
End Function  
Public Sub MyProc()   
  ' ...   
End Sub  
В начало В начало

Совет 389. Программная регистрация ActiveX DLL и OCX

Обычно регистрация (или ее отмена) ActiveX-компонентов выполняется с помощью автономной утилиты regsvr32.exe. Если необходимо выполнять процедуры регистрации в момент выполнения вашего VB-приложения, то можно воспользоваться обращением к этой утилите с помощью Shell. Однако существует еще один способ проведения таких операций, недостатком которого является необходимость «железного» включения имени нужного компонента в код программы.

Дело в том, что каждый ActiveX-компонент (ActiveX DLL или OCX) имеет функции DllRegisterServer и DllUnregisterServer, выполняющие операции регистрации/отмены регистрации над собственным компонентом. И обратиться к ним можно напрямую, как к обычной DLL-функции.

Например, если вы хотите программно проводить операции регистрации компонента COMCTL32.OCX, то в программе нужно описать две такие функции:

' функция регистрации компонента COMCTL32.OCX
Declare Function RegComCtl32 Lib "COMCTL32.OCX" Alias _  
    DllRegisterServer() As Long
' функция отмены регистрации компонента
COMCTL32.OCX Declare Function UnRegComCtl32 Lib "COMCTL32.OCX" Alias _  
    DllUnregisterServer() As Long

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

Приведем пример кода регистрации библиотеки Test.DLL, которая хранится в произвольном каталоге C:\MyApp:

Declare Function RegTestDLL Lib "Test.DLL" Alias _  
    DllRegisterServer() As Long  
Const ERROR_SUCCESS = 0& Dim retCode As Long  
On Error Resume Next  ' включаем программную обработку ошибок
ChDrive "C:"        ' Устанавливаем нужный ChDir "C:\MyApp"    ' каталог текущим
regCode = RegTestDLL()  ' регистрация Test.DLL
' анализ возможных ошибок If Err <> 0 Then
  MsgBox "Файл Test.DLL не найден"
Else  
  If regCode <> ERROR_SUCCES Then  
    MsgBox "Операция регистрации не выполнена"
  End If  
End If  
В начало В начало

Совет 390. Быстрое перемещение между процедурами

В окне кода VB передвигаться между процедурами модуля или формы можно с помощью быстрых клавиш — Ctrl + Page Down и Ctrl + Page Up.

В начало В начало

Совет 391. Как посчитать число строк в текстовом окне

Если вы определили текстовое поле как «многостроковое», может возникнуть необходимость узнать число строк. Это можно сделать с помощью такого простого кода:

Private Sub Command1_Click()  
  Dim myParas As Variant  
  myParas = Split(Text1.Text, vbNewLine)  
  MsgBox "Число строк = " & (UBound(myParas) + 1)  
End Sub  

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

Private Declare Function SendMessageAsLong Lib "user32" _  
  Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _  
  ByVal wParam As Long, ByVal lParam As Long) As Long  
Const EM_GETLINECOUNT = 186  
Private Sub Command2_Click()  
  Dim lCount As Long  
  lCount = SendMessageAsLong(Text1.hWnd, EM_GETLINECOUNT, 0, 0)  
  MsgBox "Фактическое число строк = " & lCount
End Sub  
В начало В начало

Совет 392. Как определить позицию курсора в Rich Textbox

Если в качестве текстового редактора вы используете элемент управления Rich Textbox, то полезно узнать не только число строк (о чем говорилось в предыдущем совете), но также, например, и текущую позицию курсора. Это можно сделать с помощью еще одной API-функции —  SendMessageByNum:

Private Declare Function SendMessageByNum Lib "user32" _  
 Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, _  
 ByVal wParam As Long, ByVal lParam As Long) As Long  
Private Const EM_LINEFROMCHAR = &HC9 Private Const EM_LINEINDEX = &HBB  
Public Function GetCurrentLine(TxtBox As Object) As Long  
  ' определение текущей строки в окне
  With TxtBox  
    GetCurrentLine = SendMessageByNum(.hwnd, _  
        EM_LINEFROMCHAR, CLng(.SelStart), 0&) + 1  
  End With  
End Function  
Public Function GetCurrentColumn(TxtBox As Object) As Long  
  ' определение текущей колонки в окне
  With TxtBox  
    GetCurrentColumn = .SelStart - SendMessageByNum(.hwnd, _  
      EM_LINEINDEX, -1&, 0&) + 1  
  End With  
End Function
Вот как их можно использовать:
Private Sub Command1_Click()  
  MsgBox "Текущая строка = " & GetCurrentLine(RichTextBox1)  
End Sub  
Private Sub Command2_Click()  
  MsgBox "Текущая колонка = " & GetCurrentColumn(RichTextBox1)  
End Sub  
В начало В начало

Совет 393. Программная имитация нажатия кнопок

Предположим, на вашей форме Form1 расположены несколько командных кнопок и вам нужно выполнить программную имитацию их нажатия:

Private Sub Command1_Click()  
  MsgBox "Кнопка 1 нажата!"
End Sub  
Private Sub Command2_Click()  
  MsgBox "Кнопка 2 нажата!"
End Sub

К примеру, на другой форме Form2 имеется кнопка AllCommands, щелчок которой должен вызывать нажатие двух кнопок на Form1. Обратиться непосредственно к процедурам CommandN_Click нельзя, так как они имеют статус Private. Для решения этой проблемы следует воспользоваться свойством Value, которое управляет состоянием кнопки:

Private Sub AllCommands_Click()  
  Form1.Command1.Value = True  
  Form1.Command2.Value = True  
End Sub

Установка значения Command.Value = True автоматически вызывает выполнение события Click соответствующей кнопки.

В начало В начало

Совет 394. Динамическое управление заголовками колонок DataGrid

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

Эта проблема решается элементарно: в SQL-операторе нужно просто указать альтернативные имена полей. Например, вместо

SELECT pub_id, pub_name FROM pubs  

написать

SELECT pub_id AS Учетный_Номер, pub_name AS Издатель FROM pubs  

В результате DataGrid выдаст на экран в качестве заголовков имена "Учетный_Номер" и "Издатель" вместо соответствующих идентификаторов pub_id и pub_name.

В начало В начало

Совет 395. А вы знаете о свойстве LockControls формы?

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

Оказалось, что такой режим блокировки элементов управления формы задается свойством LockControls = -1 (True), нигде не описанным и не упомянутым (кстати, в электронной документации вообще нет четкого описания свойств формы). Получается, что остановка/отмена LockControls может выполняться только непосредственным редактированием FRM-файла, которое будет выглядеть приблизительно следующим образом:

Begin VB.Form MyForm  
   Caption         =   "Форма с блокировкой компонентов"
   ...  
   LockControls    =   -1  'True  
   ...  
В начало В начало

Совет 396. Используйте функцию ExtFloodFill для цветной заливки поверхности

Те, кто работал с QuickBasic (в среде DOS), возможно, помнят, что там был оператор PAINT, который позволял заливать поверхности фигур произвольных очертаний. В системе VB этот оператор отсутствует и встроенные VB-функции позволяют выполнять заливку только «стандартных» фигур, например прямоугольника или круга, что явно недостаточно для решения многих графических задач.

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

Private Declare Function ExtFloodFill Lib "gdi32" _  
  (ByVal hDC As Long, ByVal X As Long, ByVal Y As Long, _  
  ByVal crColor As Long, ByVal wFillType As Long) As Long  

Параметр hDC — номер описателя объекта. В данном случае допустимым объектом является форма (Form) или элемент управления PictureBox, номер описателя определяется с помощью их свойства hDC. Цветовая фактура заливки задается с помощью двух свойств объекта (их нужно установить перед обращением к ExtFloodFill) — FillColor (цвет) и FillStyle (тип фактуры).

X и Y — координаты точки, из которой выполняется заливка. Однако следует иметь в виду, что они должны быть заданы в пикселах, а не в логических координатах, которые могут быть установлены свойством ScaleMode (по умолчанию Twip).

crColor и wFillType — взаимосвязанные параметры, определяющие режим выбора площади заливки:

- wFillType = 0 (FLOODFILLBORDER) — заливка выполняется в пределах фигуры, ограниченной контуром цвета crColor. Понятно, что цвет «начальной» точки заливки не может быть равным crColor.

- wFillType = 1 (FLOODFILLSURFACE) — заливка выполняется в точках площади, которые имеют цвет crColor. В этом случае, наоборот, цвет «начальной» точки заливки должен быть равным crColor.

В составе GUI API также предусмотрена функция FloodFill, которая является частным случаем ExtFloodFill (случай FLOODFILLBORDER).

Следует обратить внимание на один момент, не отмеченный в документации и обнаруженный нами в ходе эксперимента. Оказалось, что функция ExtFloodFill срабатывает, только если перед обращением к ней выполнено обращение к свойству object.Point (x, y). Впрочем, такую операцию выполнять в любом случае полезно, поскольку цвет «начальной» точки понадобится для установки параметра crColor (FLOODFILLSURFACE) или, напротив, для проверки значения этого же параметра (FLOODFILLBORDER).

На листинге 1 приведен пример процедуры Paint, которая имитирует оператор PAINT системы QB. Обратите внимание, что параметр BorderColor при обращении к ней нужно задавать только для случая FLOODFILLSURFACE — в другом режиме он определяется автоматически внутри процедуры с помощью Point (x, y).

На рис. 1 и 2 показаны возможности применения функции ExtFloodFill соответственно в режимах FLOODFILLBORDER и FLOODFILLSURFACE, а в листингах 2 и 3 приведен код формы Flood.frm и модуля AddProc.bas, которые используются в этом демонстрационном примере. Заливка выполняется в элементе управления PictureBox.

Обратите также внимание, что при рисовании начальных контуров (функция DrawShapes) последние пять линий изображаются фиксированным, синим цветом. Это сделано специально для демонстрации работы режима FLOODFILLBORDER. Ведь для этого случая граница не должна иметь «дырок», которые получаются при наложении линий других цветов. Таким образом, наш пример устойчиво будет работать только для случая границы синего цвета. Это вполне соответствует реальной ситуации — перед заливкой необходимо сделать контур нужного цвета.

КомпьютерПресс 9'2001