Access

  MS Access 2010+  |  Aplication Programming Interface API  |   VBA 7.0

• API. Funkcje zarządzania oknami.

• Okno potomne o określonym tekście na pasku tytułowym okna?

Gdy zachodzi konieczność sprawdzenia, czy otwarte jest okno którego tytuł (tekst na pasku tytułowym okna) zawiera wzorcowy ciąg znaków, powinniśmy przeszukać wszystkie okna potomne (dzieci) okna nadrzędnego (rodzica) i pobrać ich tytuły celem porównania z wzorcem.

Tytuł aplikacji MS Access

Niby prosta rzecz, jaką powinno być znalezienie okna głównego MS Access. Jest to okno klasy "OMain", którego oknem nadrzędnym (rodzicem) jest okno "Pulpit" . Wystarczy otworzyć MS Access przeczytać tytuł okna i potem spróbować znaleźć okno przekazując do funkcji wyszukującej odczytany tytuł. W celach testowych utworzyłem trzy bazy (db1,db2 i db3), otworzyłem je w różnych wersjach MS Access i odczytałem tytuły okien:

  • MS Access 2007 - „Db1 : Baza danych (Access 2007) — Microsoft Access”
  • MS Access 2010 - „Db2 : Baza danych (Access 2007 - 2010) — Microsoft Access”
  • MS Access 2016 - „Db3 : Baza danych- C:\tmp\Db3.accdb (format pliku programu Access 2007–2016) — Access”

Tytuły okien MS Access
Tytuły okien głównych aplikacji MS Access 2007+

Po otwarciu w każdej bazie formularza, wszystkie okna główne MS Access zmieniają swój tytuł na:

  • MS Access 2007 - „Db1 : Microsoft Access”
  • MS Access 2010 - „Db2 : Microsoft Access”
  • MS Access 2016 - „Db3 : Access”

 

Tytuły okien MS Access
Tytuły okien głównych aplikacji MS Access 2007+ po otwarciu formularza.

W rzeczywistości, wbrew temu co widzimy, tytuły okien głównych MS Access (pobierane przez funkcje API) nie zmieniły, cały czas były takie same, ale różniły się od widocznych na ekranie tytułów okien MS Access:

  • MS Access 2007 - „Microsoft Access — Db1 : Baza danych (Access 2007)”
  • MS Access 2010 - „Microsoft Access — Db2 : Baza danych (Access 2007 - 2010)”
  • MS Access 2016 - „Access — Db3 : Baza danych- C:\tmp\Db3.accdb (format pliku programu Access 2007–2016)”

Jedyny rozsądnym rozwiązaniem jest zmienić „Tytuł aplikacji” na swój własny. Wystarczy wybrać kolejno:
Menu: „Plik/Opcje programu Access/Bieżąca baza danych/Opcje aplikacji/Tytuł aplikacji” i  wpisać w okienko edycyjne wymyślona nazwę bazy danych. Dla takiego rozwiązania, po przekazaniu do funkcji wyszukującej wpisanej nazwy bazy danych, funkcja prawidłowo odszuka okno główne MS Access.

Niestety, ale nie jest tak prosto. Możemy przecież szukać okna MS Word (klasa "OpusApp"), czy też okna MS Excel (klasa "XLMAIN") o konkretnym tytule i w każdym z tych przypadków może być różnie. Każda wersja MS Office rządzi się swoimi prawami. A jakimi tego niestety nie wie nikt!

W wersji aplikacji pakietu „Microsoft Office 2010 dla Użytkowników Domowych i Uczniów” oraz pakietu „Microsoft Office 2007 dla Użytkowników Domowych” do tytule okna tekstu na końcu dopisywany był tekst „użytek niekomercyjny”, czy coś w tym stylu.

A w wersji 2019 .... ( ͡° ͜ʖ ͡°)

Wyszukiwanie okna po fragmencie tekstu na pasku tytułowym.

Jedynym rozwiązaniem jest wyszukiwanie okien, których tytuł zawiera poszukiwany tekst. Jeżeli wyszukiwanie ograniczymy do okien Microsoft Office, to zastosowanie opcjonalnego argumentu sWindowClass w którym przekazujemy nazwę klasy poszukiwanego okna, przyspieszy wyszukiwanie i czyni je bardziej prawdopodobnymi. Poniżej przedstawiam nazwy klas okien MS Office:

  • OMain - Access
  • OpusApp - Word
  • XLMAIN - Excel
  • PPTFrameClass - PowerPoint
  • IEFrame - Internet Explorer
  • Framework::CFrame - OneNote

Czy okno nadrzędne (rodzic) zawiera okno potomne (dziecko), którego tytuł zawiera określony ciąg znaków?

Chcąc sprawdzić, czy otwarte jest okno określonej klasy, którego tytuł (tekst na pasku tytułowym okna) zawiera określony fragment (konkretny ciąg znaków), napiszemy funkcję accFindWindowByTitle (...), która:

Public Function accFindWindowByTitle( _
                hParent As Long, _
                sWindowTitle As String, _
       Optional sWindowClass As String = "") As Long
  • Za pomocą funkcji API FindWindowEx(...) sprawdza, czy okno nadrzędne (rodzic) o uchwycie hParent zawiera okna potomne (dzieci) o określonym wzorcu nazwy klasy i tytule zawierającym przekazany w argumencie sWindowTitle tekst. Poszukiwanie okien potomnych (dzieci) odbywa się zgodnie z kolejnością Z-Order. Jeżeli nie jest określona nazwa klasy, to pobierane są kolejno uchwyty okien potomnych (dzieci) okna nadrzędnego (rodzica).

    Dla każdego okna, przy wykorzystaniu funkcji API GetWindowTextLength(...) pobierana jest długość tekstu okna. W przypadku okien zawierających tekst jest on pobierany przez funkcję GetWindowText(...) do bufora sChildTitle i sprawdzany jest, czy zawiera szukany ciąg znaków.
  • argumenty:
    • hParent
    • uchwyt okna nadrzędnego (rodzica), które będzie przeszukiwane
    • sWindowTitle
    • ciąg znaków, fragment tytułu okna jaki musi zawierać tytuł szukanego okna
    • sWindowClass
    • argument opcjonalny. Pełna nazwa klasy szukanego okna potomnego (dziecka). Jeżeli argument ten nie jest ustawiony, zostaje przekonwertowany na stałą vbNullString. Stałej tej nie można zastępować ciągiem zerowej długości "".
  • zwraca:
  • Przy powodzeniu zwraca uchwyt pierwszego znalezionego okna, pasującego do przekazanego wzorca klasy i którego tytuł zawiera określony ciąg znaków. Jeżeli klasa okna nie została określona, funkcja zwraca uchwyt pierwszego znalezionego okna, którego tytuł zawiera określony ciąg znaków. Przy niepowodzeniu zwraca ZERO
  • autor: Zbigniew Bratko
  • data: 14.02.2019
Option Compare Database
Option Explicit

#If VBA7 Then
  Public Declare PtrSafe Function FindWindowEx Lib "user32" _
         Alias "FindWindowExA" ( _
         ByVal hWndParent As LongPtr, _
         ByVal hWndChildAfter As LongPtr, _
         ByVal lpszClass As String, _
         ByVal lpszWindow As String) As LongPtr
  Public Declare PtrSafe Function GetWindowTextLength Lib "user32" _
         Alias "GetWindowTextLengthA" ( _
         ByVal hWnd As LongPtr) As Long
  Public Declare PtrSafe Function GetWindowText Lib "user32" _
         Alias "GetWindowTextA" ( _
         ByVal hWnd As LongPtr, _
         ByVal lpString As String, _
         ByVal nMaxCount As Long) As Long
#Else
  Public Declare Function FindWindowEx Lib "user32" _
         Alias "FindWindowExA" ( _
         ByVal hWndParent As Long, _
         ByVal hWndChildAfter As Long, _
         ByVal lpszClass As String, _
         ByVal lpszWindow As String) As Long
  Public Declare Function GetWindowTextLength Lib "user32" _
         Alias "GetWindowTextLengthA" ( _
         ByVal hWnd As Long) As Long
  Public Declare Function GetWindowText Lib "user32" _
         Alias "GetWindowTextA" ( _
         ByVal hWnd As Long, _
         ByVal lpString As String, _
         ByVal nMaxCount As Long) As Long
#End If


'Wyszukiwanie okna po fragmencie tekstu na pasku tytułowym
#If VBA7 Then
  Public Function accFindWindowByTitle( _
                  hParent As LongPtr, _
                  sWindowTitle As String, _
         Optional sWindowClass As String = "") As LongPtr
  Dim hChild    As LongPtr
#Else
  Public Function accFindWindowByTitle( _
                  hParent As Long, _
                  sWindowTitle As String, _
         Optional sWindowClass As String = "") As Long
  Dim hChild    As Long
#End If

Dim sClassName  As String
Dim sChildTitle As String
Dim lLenTitle   As Long

  ' sWindowClass nie może być ciągiem zerowej długości
  ' sClassName = IIf(Len(sWindowClass) = 0, vbNullString, sWindowClass)
  ' UWAGA: Jeżeli korzystać ze stałej vbNullString, nie możemy korzystać z funkcji IIf
  ' gdyż VBA przekonwertuje wartośc stałej vbNullString na ciąg zerowej długości.
  If Len(sWindowClass) = 0 Then
    sClassName = vbNullString
  Else
    sClassName = sWindowClass
  End If

  ' pobierz uchwyt pierwszego okna potomnego (dziecka)
  hChild = FindWindowEx(hParent, 0&, sClassName, vbNullString)

  Do Until hChild = 0
    ' pobierz długość tekstu okna
    lLenTitle = GetWindowTextLength(hChild)
    ' sprawdź, czy okna potomne (dziecko) zawiera tekst
    If lLenTitle > 0 Then
      ' dodaj 1, na znak końca ciągu znaków vbNullChar
      lLenTitle = lLenTitle + 1
      ' przygotuj bufor na przyjęcie tekstu okna
      sChildTitle = String(lLenTitle, vbNullChar)
      ' pobierz tekst okna do buforu sChildTitle
      lLenTitle = GetWindowText(hChild, sChildTitle, lLenTitle)
      sChildTitle = Left$(sChildTitle, lLenTitle)
      ' sprawdź, czy tytuł okna potomnego zawiera szukany tytuł okna
      If InStr(1, sChildTitle, sWindowTitle, vbTextCompare) > 0 Then
        ' zwróć uchwyt znalezionego okna
        accFindWindowByTitle = hChild
        Exit Function
      End If
    End If

    'pobieraj kolejno uchwyty okien potomnych, bez względu na tytuł okna
    hChild = FindWindowEx(hParent, hChild, sClassName, vbNullString)
  Loop
  
End Function