Советы тем, кто программирует на 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