1、
在Windows編程中,向文本框控件、列表控件、按鈕控件等是我們最常接觸的控件了。但是在VB中這些控件有時無法實現我們的需要。在這時,我們只要簡單的利用Windows API函數就可以擴充這些控件的功能了。
顧名思義,SendMessage函數就是向窗口(這里的窗口指的是向按鈕、列表框、編輯框等具有hWnd屬性的控件)發送消息的函數,該函數的定義如下:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
其中hwnd指定接受消息的窗口,參數wMsg指定消息值,參數wParam lParam分別定義傳遞到窗口的附加參數。而在Windows系統的很多消息中,有一些不僅僅是提供一個窗口消息那么簡單。它們可以控制窗口的動作和屬性。下面我將分次向向大家介紹SendMessage函數在擴充基本控件功能方面的應用。
一、列表(ListBox)控件
在Windows中,有一系列的以LB_開頭的列表消息,這里介紹的就是利用LB消息控制的ListBox的應用
1、使列表中光標移動到不同的列表項上有不同的提示(ToolTip)
在列表框控件中有一個ToolTipText屬性,該屬性決定了當光標在列表框上移動時出現的提示文字。但是如何使得當光標在不同的列表項上移動時的提示文字也不同呢?問題的關鍵是要知道在光標移動時光標所在的列表項的索引,使用SendMessage函數發送LB_ITEMFROMPOINT消息就可以獲得。下面是程序范例:
Option Explicit
Const LB_ITEMFROMPOINT = &H1A9
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
Private Sub Form_Load()
Dim i
For i = 1 To 200
List1.AddItem Str(i) + " Samples in this list is " + Str(i)
Next i
End Sub
Private Sub List1_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Dim lXPoint As Long
Dim lYPoint As Long
Dim lIndex As Long
If Button = 0 Then '確定在移動鼠標的同時沒有按下功能鍵或者鼠標鍵
'獲得光標的位置,以像素為單位
lXPoint = CLng(X / Screen.TwipsPerPixelX)
lYPoint = CLng(Y / Screen.TwipsPerPixelY)
'
With List1
'獲得 光標所在的標題行的索引
lIndex = SendMessage(.hwnd, LB_ITEMFROMPOINT, 0, _
ByVal ((lYPoint * 65536) + lXPoint))
'將ListBox的Tooltip設置為該標題行的文本
If (lIndex >= 0) And (lIndex <= .ListCount) Then
.ToolTipText = .List(lIndex) 'Return the text = .list(lIndex)
Else
.ToolTipText = ""
End If
End With
End If
End Sub
首先在Form1中加入一個ListBox控件,然后再將上面的代碼加入到Form1的代碼窗口中。運行程序,當光標在列表中移動時,可以看到根據光標所在的不同的列表項,提示文字也不相同。
2、向列表中加入橫向滾動條使得可以瀏覽長列表項
當向列表中加入的列表項超出了列表的顯示范圍后,列表並不會出現橫向滾動條讓你可以通過滾動來瀏覽項目的全部內容。利用LB_SETHORIZONTALEXTENT消息可以設置列表的橫向滾動條以及滾動長度。下面是范例程序:
Option Explicit
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Declare Function DrawText Lib "user32" Alias "DrawTextA" _
(ByVal hdc As Long, _
ByVal lpStr As String, _
ByVal nCount As Long, _
lpRect As RECT, _
ByVal wFormat As Long) As Long
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
Const LB_SETHORIZONTALEXTENT = &H194
Const DT_CALCRECT = &H400
Public Function ListTextWidth(ByRef lstThis As ListBox) As Long
Dim i As Long
Dim tR As RECT
Dim lW As Long
Dim lWidth As Long
Dim lHDC As Long
With lstThis.Parent.Font
.Name = lstThis.Font.Name
.Size = lstThis.Font.Size
.Bold = lstThis.Font.Bold
.Italic = lstThis.Font.Italic
End With
lHDC = lstThis.Parent.hdc
'便歷所有的列表項以找到最長的項
For i = 0 To lstThis.ListCount - 1
DrawText lHDC, lstThis.List(i), -1, tR, DT_CALCRECT
lW = tR.Right - tR.Left + 8
If (lW > lWidth) Then
lWidth = lW
End If
Next i
'返回最長列表項的長度(像素)
ListTextWidth = lWidth
End Function
Private Sub Form_Load()
Dim astr As String
Dim i
Dim l As Long
l = List1.FontSize * 20 / Screen.TwipsPerPixelX
For i = 1 To 10
astr = astr + "我們This is a very long item " + Str(i)
Next i
List1.AddItem astr + "aaa"
'加入一個很長的列表項
l = ListTextWidth(List1)
SendMessage List1.hwnd, LB_SETHORIZONTALEXTENT, l, 0
End Sub
首先在Form1中加入一個ListBox控件,然后再將上面的代碼加入到Form1的代碼窗口中。運行程序,可以看到列表中出現了橫向滾動條,而且滾動范圍正好是列表項的長度。
3、使列表可以響應用戶擊鍵
有時我們需要列表根據用戶的敲入字符串自動調整列表的ListIndex到最接近的列表項,就象VB中動態感應用戶輸入控件屬性的編輯器一樣。問題的關鍵是如何在列表中查找含有指定字符串的列表項,使用LB_FINDSTRING消息可以在列表中查找指定字符串。下面是范例:
Private Declare Function SendMessageStr Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As String) As Long
Const LB_FINDSTRING = &H18F
Dim astr As String
Private Sub Form_KeyPress(KeyAscii As Integer)
Dim l As Long
astr = astr + Chr(KeyAscii)
l = SendMessageStr(List1.hwnd, LB_FINDSTRING, -1, astr)
If l Then
List1.ListIndex = l
End If
End Sub
Private Sub Form_Load()
'向List中加入列表項
For i = 65 To 85
For j = 65 To 85
List1.AddItem Chr(i) + Chr(j)
Next j
Next i
End Sub
Private Sub List1_DblClick()
'清除原來的查找字符串
astr = ""
End Sub
Private Sub List1_KeyPress(KeyAscii As Integer)
'如果按下的是字母鍵就將擊鍵消息傳遞到Form1
If ((KeyAscii >= 65 And KeyAscii <= 90) Or (KeyAscii >= 97 _
Or KeyAscii <= 122)) Then
KeyAscii = 0
End If
End Sub
首先在Form1中加入一個ListBox控件,然后再將上面的代碼加入到Form1的代碼窗口中。並將List1的Sorted屬性設置為True。運行程序,在列表中敲入字符,例如"av" "gm",列表就會高亮顯示相近的列表項,雙擊列表就可以清除原來的輸入。
在上一篇文章中我向大家介紹了關於ListBox類控件消息的應用,在這一章我將向大家介紹如何利用消息操控TextBox類控件。
1、獲得光標所在的行和列
一般的比較完善的文本編輯器一般都有在狀態欄中顯示當前光標所在行和列的功能。利用SendMessage向TextBox控件發送編輯控件類型消息。也可以實現這樣的功能。下面首先來看程序,然后再分析。
首先在VB中建立一個新工程,並在Form1中加入一個TextBox控件和兩個Label控件。將TextBox控件的MultiLine屬性設置為True。然后在Form1的代碼窗口中加入如下代碼:
Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageW" _
(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function SendMessageByRef Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Long, ByVal wMsg As Long, wParam As Long, _
lParam As Long) As Long
Const EM_LINEFROMCHAR = &HC9
Const EM_LINEINDEX = &HBB
Const EM_GETLINE = &HC4
Const EM_GETSEL = &HB0
Dim iLineX, iLineY As Long
Sub GetCurPos(txtA As TextBox)
Dim l, l1, l2 As Long
Dim astr As String * 256
l = SendMessage(txtA.hwnd, EM_LINEINDEX, -1, 0)
iLineY = SendMessage(txtA.hwnd, EM_LINEFROMCHAR, l, 0)
SendMessageByRef txtA.hwnd, EM_GETSEL, l1, l2
iLineX = l1 - l
Label1.Caption = "列:" + Str(iLineX)
Label2.Caption = "行:" + Str(iLineY)
End Sub
Private Sub Form_Load()
Dim iFile
Dim astr As String
Label1.Height = 300: Label2.Height = 300
Text1.Left = 0: Text1.Top = 0
Text1.Text = ""
Label1.Caption = ""
Label2.Caption = ""
iFile = FreeFile
Open "C:\windows\readme.txt" For Input As #iFile
Do
Line Input #iFile, astr
Text1.Text = Text1.Text + astr + vbCrLf
Loop Until EOF(iFile)
Close iFile
End Sub
Private Sub Form_Resize()
Label1.Top = Me.ScaleHeight - 300
Label2.Top = Me.ScaleHeight - 300
Label1.Left = 0: Label2.Left = 1200
Label1.Width = 1200
Label2.Width = 1200
Text1.Width = Me.ScaleWidth
Text1.Height = Me.ScaleHeight - Label1.Height
End Sub
Private Sub Text1_Click()
GetCurPos Text1
End Sub
Private Sub Text1_KeyUp(KeyCode As Integer, Shift As Integer)
GetCurPos Text1
End Sub
在運行程序前,確保在你的硬盤上有 c:\windows\readme.txt 這個文件。否則程序會出錯。然后運行程序。當在編輯文本時,可以看到在窗口底部可以顯示當前光標所在的行、列值。在上面的程序中。我們首先發送EM_LINEINDEX消息,發送該消息可以返回某一行的第一個字符在整個文本控件中的位置,如果wParam參數設置為-1,則返回當前行的字符位置。然后發送EM_LINEFROMCHAR,發送該消息可以根據參數wParam指定的字符位置返回該字符所在的行號,文本第一行的位置為0。這樣使用這兩個消息就獲得當前光標所在的行號。要取得列號,首先發送EM_GETSEL消息,發送該消息返回當前被選中文本的起始位置,如果沒有文本被選中,則返回當前光標所在字符在文本中的位置。由於上面的EM_LINEINDEX消息返回的是當前行的第一個字符在文本中的位置。所以將兩值相減,就是光標所在字符的列位置。在上面的程序中,如果你的文本中有中文字符的話,當你的光標在中文字符中移動一個位置,你會看到標簽中的列位置增加了2,這是由於SendMessage發送的消息所得到的結果是不支持中文的,它將一個中文字算做兩個字符。這也算是程序中的一個Bug吧(這也就是為什么我要使用EM_GETSEL消息而不直接使用TextBox控件的SelStart屬性來獲取光標所在字符位置了,因為如果使用SelStart返回的值將一個中文算一個字符,同EM_LINEINDEX返回值相減有可能得到負值).
2、聲明API
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long '聲明的 API
'API常量申明
Public Const WM_SETHOTKEY = &H32
Public Const HOTKEYF_SHIFT = &H1
Public Const HOTKEYF_CONTROL = &H2
Public Const HOTKEYF_ALT = &H4
例子: 用sendmessage 給窗體發送個消息! 讓他顯示出來
Private Sub Form_Load()
Dim l As Long
Dim wHotkey As Long
wHotkey = (HOTKEYF_ALT Or HOTKEYF_CONTROL) * (2 ^ 8) + 66 '設置熱鍵
l = SendMessage(Me.hwnd, WM_SETHOTKEY, wHotkey, 0) 'WM_SETHOTKEY設置熱鍵消息
MsgBox "只需按Ctl+Alt+B 即可調出窗體"
Me.WindowState = 1 '窗體最小化
End Sub