Visual Basic Românesc  

 

Lista VB Ro
Carti
Surse
Articole
Programe
Legaturi
Pareri

 

 

 

Subclasificarea

       Un alt subiect deosebit de interesant pentru Visual Basic il constituie subclasificarea ('subclassing'). Aceasta este un concept care combina programarea din Visual Basic cu programarea cu ajutorul functiilor si mesajelor Windows pentru realizarea unor lucruri care in mod direct nu se pot realiza in Visual Basic. De fapt are loc o filtrare a mesajelor si o prelucrare a lor dupa necesitatile noastre.
       Sa luam un mic exemplu: cum se poate realiza, pt. un meniu, ca la trecerea cursorului peste o optiune sa fie afisata pe status bar citeva explicatii legate de optiunea respectiva din meniu. (la fel ca Windows Explorer-ul de exemplu)
       De fapt solutia consta in schimbarea functiei de prelucrare a mesajelor cu o functie definita de noi in care se verifica un anume mesaj. In cazul in care acesta apare prelucram acest mesaj dupa care apelam vechea functie de prelucrare a mesajelor. Daca mesajul asteptat de noi nu apare pur si simplu se apeleaza vechea functie de prelucrare a mesajelor. Schimbarea functiei de tratarea mesajelor o schimbam cu SetWindowLong la care trebuie sa-i dam 3 parametrii: hWnd-ul fereastrei, constanta GWL_WNDPROC si adresa noi functii de tratarea mesajelor, iar functia SetWindowLong returneaza vechea procedura de tratarea mesajelor. Adresa noii functii o dam cu ajutorul noului operator AddressOf (introdus in VB5). Antetul noii functii de tratare a mesajelor trebuie sa fie identic cu antetul unei functii Windows de tratare a mesajelor, in exemplul nostru
Public Function SystemMenuCallBack(ByVal lnghwnd As Long, ByVal lngMessage As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
unde lnghwnd este identificatorul ferestrei, lngMessage este mesajul primit, iar wParam si lParam sint parametri aditionali mesajului. In final se reface procedura de prelucrarea mesajelor la valoarea initiala prin apelul Functiei SetWindowLong cu valoarea initiala. Apelul vechii proceduri de prelucrare a mesajelor se face cu functia CallWindowProc care in plus fata de antetul unei functii de prelucrare a mesajelor mai are specificat si identificatorul pentru functia de prelucrare a mesajelor dorita (in cazul nostru se apeleaza cu identificatorul vechii functii de prelucrarea mesajelor).

Sa vedem acum programul concret pt. aceasta.
Mai intii punem pe o forma (frmMenuSample) un meniu de forma

Optiune1	Optiune		Exit
  Suboptiune11    Suboptiune21
  ------------
  Suboptiune12
si un statusbar (Statusbar1) cu Style = 1-sbrSimple
Adaugam un modul la proiect in care scriem:

Const WM_MENUSELECT = &H11F	'mesajele tratate de noi
Const WM_EXITMENULOOP = &H212	'    - // -
Const GWL_WNDPROC = (-4)	'constanta pentru specificarea functiei de prelucrarea mesajelor
Private mlngOldProcessID As Long ' identificatorul vechii functii de prelucrarea mesajelor

Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
 (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, _
 ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
 (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Sub SystemMenuSubClass()
'instaleaza noua functie de prelucrarea mesajelor si in acelasi timp salveaza
'identificatorul vechii functii de prelucrarea mesajelor (in mlngOldProcessID)
 mlngOldProcessID = SetWindowLong(frmMenuSample.hwnd, GWL_WNDPROC, _
   AddressOf SystemMenuCallBack)
End Sub

Public Function SystemMenuCallBack(ByVal lnghwnd As Long, ByVal lngMessage As Long, _
  ByVal wParam As Long, ByVal lParam As Long) As Long
'aceasta este noua functie de prelucrarea a mesajelor definita de noi in care sint 
'prelucrate doar mesajele care ne intereseaza
    If lngMessage = WM_MENUSELECT Then
      'iau ultimele 4 cifre hexa ale lui wParam
      Select Case Val(Right(CStr(Hex(wParam)), 4))
          Case 0
             frmMenuSample.StatusBar1.SimpleText = "Optiune 1"
          Case 1
             frmMenuSample.StatusBar1.SimpleText = "Optiune 2"
          Case 2
             frmMenuSample.StatusBar1.SimpleText = "Sub Optiune 1 - 1"
          Case 4
             frmMenuSample.StatusBar1.SimpleText = "Sub Optiune 1 - 2"
          Case 6
             frmMenuSample.StatusBar1.SimpleText = "Sub Optiune 2 - 1"
          Case 7
             frmMenuSample.StatusBar1.SimpleText = "Terminarea programului"
       End Select
    End If
    If lngMessage = WM_EXITMENULOOP Then frmMenuSample.StatusBar1.SimpleText = "Ready"
'apeleaza vechea functie de prelucrearea mesajelor indiferent daca am prelucrat si noi mesajul
'functia va returna valoarea returnata de vechea functie de prelucrarea mesajelor
SystemMenuCallBack = CallWindowProc(mlngOldProcessID, lnghwnd, lngMessage, wParam, lParam)
End Function

Public Sub SystemMenuRestore()
'reface vechea procedura de prelucrarea mesajelor
Call SetWindowLong(frmMenuSample.hwnd, GWL_WNDPROC, mlngOldProcessID)
End Sub
Citeva observatii legate de functia SystemMenuCallBack (aceasta poate avea orice alt nume dar trebuie sa respecte tipul parametrilor pe care ii are).
In cazul nostru ne intereseaza doua mesaje si anume WM_MENUSELECT si WM_EXITMENULOOP (primul apare cind selectam o optiune din meniu iar al doilea mesaj apare cind meniul pierde focusul si am iesit din meniu). Daca s-a primit primul mesaj atunci in wParam o sa avem optiunea din meniu selectata incepind cu 0 (=numarul primei optiune) pina la 7 (=numarul ultimei optiuni). In functie de optiunea selectata afisez textul corespunzator pe statusbar.
Daca am terminat lucrul cu meniul (la aparitia mesajului WM_EXITMENULOOP) afisez pe statusbar 'Ready'.

Forma are urmatorul cod:


Private Sub mnuExit_Click()
Unload Me
End Sub

Private Sub Form_Load()
StatusBar1.SimpleText = "Salut !"
SystemMenuSubClass  'aici instalez noua procedura de tratarea mesajelor
End Sub

Private Sub Form_Unload(Cancel As Integer)
'aici refac vechea procedura de tratarea mesajelor
SystemMenuRestore
End Sub

Private Sub mnusub11_Click()
MsgBox "SubOptiune1 la Optiune1"
End Sub

Private Sub mnusub12_Click()
MsgBox "SubOptiune2 la Optiune1"
End Sub

Private Sub mnusub21_Click()
MsgBox "SubOptiune1 la Optiune2"
End Sub

Un alt exemplu unde se poate folosi aceasta metoda de programare ar putea fi pentru cazul cind ne propunem sa realizam un program care sa stie de drag&drop, mai exact sa gasim numele fisierelor trase peste forma noastra (avind numele fisierelor mai departe se poate face orice cu aceste fisiere). De fapt este o combinatie de subclasificare si de utilizare a functiilor API Windows.
Punem pe o forma o lista (List1), 2 butoane de comanda: unul pentru terminare (cmdExit) si unul pentru stergerea listei (cmdSterge) si un checkbox pentru a accepta sau nu drag&drop-ul (chkAccept). De asemenea, adaugam la proiect un modul in care scriem:


Declare Sub DragAcceptFiles Lib "shell32.dll" (ByVal hwnd As Long, ByVal fAccept As Long)
Declare Sub DragFinish Lib "shell32.dll" (ByVal HDROP As Long)
Declare Function DragQueryFile Lib "shell32.dll" Alias "DragQueryFileA" _
 (ByVal HDROP As Long, ByVal UINT As Long, ByVal lpStr As String, ByVal ch As Long) As Long

Public Const WM_DROPFILES = &H233
Const GWL_WNDPROC = (-4)
Private mlngOldProcessID As Long

Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _
 (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal MSG As Long, _
 ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
 (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Sub SystemDragDropSubClass()
'instaleaza noua functie de prelucrarea mesajelor si in acelasi timp salveaza
'identificatorul vechii functii de prelucrarea mesajelor (in mlngOldProcessID)
 mlngOldProcessID = SetWindowLong(frmDragDrop.hwnd, GWL_WNDPROC, _
   AddressOf SystemDragDropCallBack)
End Sub

Public Function SystemDragDropCallBack(ByVal lnghwnd As Long, ByVal lngMessage As Long, _
  ByVal wParam As Long, ByVal lParam As Long) As Long
Dim lpszFile As String * 255
Dim m As Boolean, nr As Long, i As Integer, x As Long
    If lngMessage = WM_DROPFILES Then
        nr = DragQueryFile(wParam, -1, lpszFile, 255)
        For i = 0 To nr - 1
            x = DragQueryFile(wParam, i, lpszFile, 255)
            frmDragDrop.List1.AddItem Left$(lpszFile, x)
        Next
        DragFinish (wParam)
        End If
SystemDragDropCallBack = CallWindowProc(mlngOldProcessID, lnghwnd, lngMessage, wParam, lParam)
End Function

Public Sub SystemDragDropRestore()
'reface vechea procedura de prelucrarea mesajelor
Call SetWindowLong(frmDragDrop.hwnd, GWL_WNDPROC, mlngOldProcessID)
End Sub
De fapt dupa acelasi principiu tratam in noua noastra functie pt. prelucrarea mesajelor doar mesajul WM_DROPFILES care apare in momentul cind am lasat fisiere peste forma noastra. Numele acestor fisiere se obtin prin apelarea functiilor API Windows DragQueryFile cu urmatorii parametri: identificatorul unei structuri a fisierelor lasate pe forma, indexul fisierului in structura anterioara (daca este -1 ne da numarul fisierelor), un bufer pt. numele unui fisier si lungimea numelui fisierului de index dat.
Deci in momentul aparitiei mesajului WM_DROPFILES citesc numarul fisierelor, iar apoi adaug fiecare fisier in lista, dupa care apelez functia DragFinish care elibereaza memoria ocupata de numele fisierelor. In continuare apelez vechea functie de tratarea mesajelor.
Programul din forma frmDragDrop este urmatorul:

Private Sub chkAccept_Click()
'Accepta sau nu drag&drop-ul fisierelor
If chkAccept.Value = vbChecked Then
DragAcceptFiles Me.hwnd, True
Else
DragAcceptFiles Me.hwnd, False
End If
End Sub

Private Sub cmdExit_Click()
Unload Me
End Sub

Private Sub cmdSterge_Click()
List1.Clear
End Sub

Private Sub Form_Load()
'initial accepta drag&drop-ul fisierelor
DragAcceptFiles Me.hwnd, True
'instalarea noii functii de prelucrare a mesajelor
SystemDragDropSubClass
End Sub

Private Sub Form_Unload(Cancel As Integer)
'apelul procedurii de refacere
SystemDragDropRestore
End Sub
Acceptarea sau nu a drag&drop-ului fisierelor se face prin apelul unei alte functii API Windows si anume DragAcceptFiles care are 2 parametrii: identificatorul ferestrei si o valoare booleana care spune daca este acceptat sau nu drag&drop-ul.
Aceasi problema mai poate avea o solutie de aceasta data apelind la functiile API PeekMessage/TranslateMessage/DispatchMessage. Pt. asta se pune la Project Properties -> Starup Object = Sub Main si scrie intr-un modul urmatoarele (Form1 raminind nemodificata):

Declare Sub DragAcceptFiles Lib "shell32.dll" (ByVal hwnd As Long, ByVal fAccept As Long)
Declare Sub DragFinish Lib "shell32.dll" (ByVal HDROP As Long)
Declare Function DragQueryFile Lib "shell32.dll" Alias "DragQueryFileA" _
  (ByVal HDROP As Long, ByVal UINT As Long, ByVal lpStr As String, ByVal ch As Long) As Long

Declare Function PeekMessage Lib "user32" Alias "PeekMessageA" (lpMsg As MSG, _
  ByVal hwnd As Long, ByVal wMsgFilterMin As Long, ByVal wMsgFilterMax As Long, _
  ByVal wRemoveMsg As Long) As Long
Declare Function TranslateMessage Lib "user32" (lpMsg As MSG) As Long
Declare Function DispatchMessage Lib "user32" Alias "DispatchMessageA" (lpMsg As MSG) As Long
Declare Function WaitMessage Lib "user32" () As Long

Type POINTAPI
        x As Long
        y As Long
End Type

Type MSG
    hwnd As Long
    message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As POINTAPI
End Type

Public Const WM_QUIT = &H12
Public Const WM_DROPFILES = &H233
Public Const PM_NOREMOVE = &H0
Public Const PM_REMOVE = &H1

Sub Main()
Dim t$
Dim lpMsg As MSG
Dim lpszFile As String * 255
Dim nr As Long, i As Integer, x As Long
Form1.Show
DragAcceptFiles Form1.hwnd, True
Do
 If PeekMessage(lpMsg, Form1.hwnd, 0&, 0&, PM_REMOVE) Then
    x = TranslateMessage(lpMsg)
    x = DispatchMessage(lpMsg)
    If lpMsg.message = WM_DROPFILES Then
        nr = DragQueryFile(lpMsg.wParam, -1, lpszFile, 255)
        For i = 0 To nr - 1
            x = DragQueryFile(lpMsg.wParam, i, lpszFile, 255)
            t$ = t$ & Left$(lpszFile, x) & vbCrLf
        Next
        DragFinish (lpMsg.wParam)
        MsgBox t$
        t$ = ""
     End If
 Else
    DoEvents
    x = WaitMessage
End If
Loop Until lpMsg.message = WM_QUIT
End Sub
De fapt creem un ciclu do...loop in care citim toate mesajele cu PeekMessage (+TranslateMessage +DispatchMessage pt. diverse prelucrari ale mesajelor) pina la aparitia mesajului WM_QUIT (de terminarea executiei).
Daca este mesajul WM_DROPFILES atunci il prelucram dupa cum vrem noi (citesc numele fisierelor lasate pe forma si le afisez cu MsgBox). Daca nu este nici un mesaj in coada, dam controlul Windowsului pt. prelucrarea altor evenimente si asteptam aparitia unui mesaj pt. programul nostru (cu WaitMessage).

Desi se pot face lucruri spectaculoase folosindu-se aceasta tehnica trebuie lucrat cu mare atentie pentru ca este foarte usor sa gresesti, iar aceste erori pot duce la blocarea sistemului sau doar inchiderea Visual Basicului deoarece se lucreaza cu elemente interne Windows-ului.

Inapoi la articole

Caut cu FreeFind

Doar in VB_Ro
Intreg Web-ul

 

 

 

 


1