由于需要维护很多的VB代码,而这些代码中,对基础资料的处理清一色的都是采用ComBox控件来实现基础资料的列表显示,把基础资料的fnumber和fname放到comBox一个项里面,通过在其中加50个空格来区分。效果图如下:
又不想专门写个自定义的控件,于是我结合就用了子类化和VB自带的事件机制对TextBox进行了扩展。关键代码如下,下面的是对窗体进行子类化,当窗体上的文本控件自动获得焦点的时,把控件引用保存到我的自定义类中,在自定义类中捕获textBox的change事件。这个是个半成品,只是提供了一个思路。
@H_403_14@Public Function SubWndProc(ByVal hwnd As Long,ByVal uMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
Select Case uMsg
Case WM_ACTIVATE
If (wParam And &HFFFF) = WA_INACTIVE Then
DebugPrint "WM_ACTIVATE,失去激活"
End If
If (wParam And &HFFFF) = WA_CLICKACTIVE Then
DebugPrint "WM_ACTIVATE,鼠标激活"
Debug.Print CStr(lParam)
If lParam = Form1.hwnd Then
DebugPrint "激活的窗体句柄是FORM1.HWND"
End If
If lParam = Form1.Text1.hwnd Then
DebugPrint "激活的窗体句柄是FORM1.HWND"
End If
End If
If (wParam And &HFFFF) = WA_ACTIVE Then
DebugPrint "WM_ACTIVETE,非鼠标激活"
End If
Case WM_KILLFOCUS
DebugPrint "WM_KillFocus"
Case WM_COMMAND
'收到WM_COMMAND后,先判断是哪个控件发送的
'不同控件的通知码不一样,其对应的消息类型不一样
Dim bFind As Boolean
Dim i As Integer
i = Form1.Controls.Count - 1
bFind = False
Do While (Not bFind And i >= 0)
If Form1.Controls(i).hwnd = lParam Then
bFind = True
'找到控件后,判断控件的类型
Select Case TypeName(Form1.Controls(i))
Case "TextBox"
If CInt((wParam / &H10000)) = EN_SETFOCUS Then
DebugPrint "EN_SETFOCUS"
If oTextEx Is Nothing Then
Set oTextEx = New ClsTextBoxEx
End If
oTextEx.Attach Form1.Controls(i)
oTextEx.SetConnString "Provider=sqlNCLI10;Password=k3manager;Persist Security Info=True;User ID=sa;Initial Catalog=AIS20140709093851;Data Source=."
End If
If CInt(wParam / &H10000) = EN_KILLFOCUS Then
DebugPrint "EN_KILLFOCUS"
If Not oTextEx Is Nothing Then
Set oTextEx = Nothing
End If
End If
End Select
End If
i = i - 1
Loop
Case WM_CLOSE
DebugPrint " FORM WM_CLOSE"
If Not oTextEx Is Nothing Then Set oTextEx = Nothing
Case WM_DESTROY
DebugPrint " FORM WM_DESTORY"
End Select
SubWndProc = CallWindowProc(lpPreProc,hwnd,uMsg,wParam,lParam)
End Function
下面是我的自定义类,定义了一个withevent的textBox变量,扩展了 Change事件。
下面是我的自定义类,定义了一个withevent的textBox变量,扩展了 Change事件。
@H_403_14@Private strCnn As String
Private strTable As String
Private WithEvents mtxt As VB.TextBox
Private lPreHwnd As Long
Private lNowHwnd As Long
Private mCnn As ADODB.Connection
Private mfrmAc As frmAutoComlete
'strCnn = "Provider=sqlNCLI10;Password=k3manager;Persist Security Info=True;User ID=sa;Initial Catalog=AIS20140709093851;Data Source=."
Public Sub Attach(ByVal o As VB.TextBox)
If Not mtxt Is Nothing Then Set mtxt = Nothing
If Not gdest Is Nothing Then Set gdest = Nothing
Set mtxt = o
Set gdest = o
End Sub
Public Sub SetConnString(ByVal param As String)
strCnn = param
LoadResource
End Sub
Public Sub DestroyResource()
'去除cnn的连接
If Not mCnn Is Nothing Then
If mCnn.State = adStateOpen Then mCnn.Close
Set mCnn = Nothing
End If
'卸载窗体
If Not mfrmAc Is Nothing Then
Unload mfrmAc
Set mfrmAc = Nothing
End If
End Sub
Public Function LvHWnd() As Long
LvHWnd = 0
If Not mfrmAc Is Nothing Then LvHWnd = mfrmAc.ListView1.hwnd
End Function
Private Sub LoadResource()
'建立连接对象
If mCnn Is Nothing Then Set mCnn = New ADODB.Connection
mCnn.ConnectionString = strCnn
mCnn.CursorLocation = adUseClient
'装载窗体
Set mfrmAc = New frmAutoComlete
Load mfrmAc
'初始化窗体上的资源
mfrmAc.ListView1.ColumnHeaders.Add 1,"fnumber",1050
mfrmAc.ListView1.ColumnHeaders.Add 2,"fname",1500
mfrmAc.Timer1.Enabled = False
End Sub
Private Sub mtxt_Change()
Dim oRst As ADODB.Recordset
Dim olv As ListView
Dim strsql As String
Dim lngHeights As Long
mfrmAc.Visible = False
mfrmAc.Timer1.Enabled = False
'根据mtxt的内容来拼接sql
If Len(mtxt.Text) = 0 Then Exit Sub
If Len(mtxt.Text) = 1 And Left(mtxt.Text,1) = Chr(13) Then
'带出上一轮的输入
Exit Sub
Else
strsql = "select top 10 fnumber,fname from t_item where fitemclassID = 4 and ( fnumber like '%" & mtxt.Text & "%' or fname like '%" & mtxt.Text & "%')"
End If
If mCnn.State = adStateClosed Then mCnn.Open
Set oRst = mCnn.Execute(strsql,adCmdText)
Set oRst.ActiveConnection = Nothing
mCnn.Close
If oRst Is Nothing Or oRst.RecordCount = 0 Then GoTo TXT
oRst.MoveFirst
'ListView控件初始化
lngHeights = 0
Set olv = mfrmAc.ListView1
olv.ListItems.Clear
While Not oRst.EOF
Dim ListItem As ListItem
Set ListItem = mfrmAc.ListView1.ListItems.Add()
ListItem.Text = CStr(oRst!fnumber)
ListItem.SubItems(1) = CStr(oRst!fname)
lngHeights = lngHeights + ListItem.Height
oRst.MoveNext
Wend
Dim lpLv As POINTAPI
lpLv.x = mtxt.Left / Screen.TwipsPerPixelX
lpLv.y = (mtxt.Top + mtxt.Height) / Screen.TwipsPerPixelY
ClientToScreen mtxt.Container.hwnd,lpLv
'SetParent mfrmAc.hwnd,mtxt.Container.hwnd
MoveWindow mfrmAc.hwnd,lpLv.x,lpLv.y,2550 / Screen.TwipsPerPixelX,(lngHeights + 30) / Screen.TwipsPerPixelY,0
mfrmAc.Timer1.Enabled = True
ShowWindow mfrmAc.hwnd,SW_SHOWNOACTIVATE
SetWindowPos mfrmAc.hwnd,HWND_TOPMOST,SWP_NOSIZE Or SWP_NOMOVE Or SWP_NOACTIVATE
'UpdateWindow mfrmAc.hwnd
TXT:
If Not oRst Is Nothing Then
If oRst.State = adStateOpen Then oRst.Close
Set oRst = Nothing
End If
End Sub
Private Sub Class_Terminate()
'去除绑定
If Not mtxt Is Nothing Then Set mtxt = Nothing
If Not gdest Is Nothing Then Set gdest = Nothing
'卸载资源
DestroyResource
End Sub
Private Sub mtxt_KeyDown(KeyCode As Integer,Shift As Integer)
Dim olv As ListView
Dim xlm As ListItem
Dim rows As Long
If KeyCode = 40 And mfrmAc.Visible Then
'向下键
Set olv = mfrmAc.ListView1
Set xlm = olv.SelectedItem
rows = olv.ListItems.Count
If xlm.Index = rows Then Exit Sub
olv.ListItems(xlm.Index + 1).Selected = True
End If
If KeyCode = 38 And mfrmAc.Visible Then
'向上键
Set olv = mfrmAc.ListView1
Set xlm = olv.SelectedItem
rows = olv.ListItems.Count
If xlm.Index = 1 Then Exit Sub
olv.ListItems(xlm.Index - 1).Selected = True
End If
If KeyCode = 13 Then
'回车键
If mfrmAc.Visible Then
'若是有弹出框的话,取弹出框选择行
Set olv = mfrmAc.ListView1
Set xlm = olv.SelectedItem
mtxt.Text = xlm.SubItems(1)
mfrmAc.Visible = False
End If
End If
End Sub
结果是最终效果实现了,但是弹出框却无法响应鼠标事件,只能通过键盘来进行选择。VB毕竟已经过时了,不像MFC,C#的WinForm那么方便的对窗体进行扩展。