Excel Forum - Porady, Pomoc,  Excel Help, Excel FAQ Strona Główna
 FAQ  RegulaminRegulamin  Szukaj   Użytkownicy   Grupy   Rejestracja   Profil   Twoje wiadomości   Zaloguj 


Poprzedni temat «» Następny temat
ID tematu: 68845 Skopiuj do schowka Modyfikacja VB_PredeclaredId
Autor Wiadomość
Rafał B.
Stały bywalec Excelforum



Wersja: Win Office 2016
Pomógł: 36 razy
Posty: 253
Wysłany: 06-09-2020, 01:01   Modyfikacja VB_PredeclaredId

__WSTĘP_______
Obiecałem @Artikowi nakreślenie paru zdań o problematyce Userformów - dlaczego wielu MVP preferuje .Hide nad .Unload. Ale pozwolę sobie na rozbicie tematu na dwa krótkie. Po pierwsze, żeby całość była bardziej intuicyjna. A drugi powód: ostatnio nauczyłem się niezwykle przydatnego "tricku", który zwiększa wygodę używania naszego kodu. Ten trick ściśle łączy się z tym tematem Userformów, więc okazja jest podwójna. Większość profesjonalistów pewnie o nim wie, ale chyba nie wszyscy, dlatego chciałbym się nim podzielić bez zbędnego zagłębiania się w teorię.

__PRZYKŁAD______
Jak wszyscy(?) wiemy Userformy są najzwyklejszymi klasami, jedyna różnica to dodatkowa warstwa interfejsu graficznego. Część mniej zorientowanych koderów VBA możę się tutaj zbulwersować i słusznie zauważyć:
Cytat:
userform w przeciwieństwie do klasy nie wymaga stworzenia słowem kluczowym New

W skoroszycie posiadamy (1) Userform1 i (2) klasę class1, w której to mamy następujący kod:
Kod:
'  class1
Public Sub Show()

    MsgBox "ok!"
    End Sub

Natomiast w zwykłym module module1:
Kod:
' module1
Sub TestMyForm()
   
    UserForm1.Show
    End Sub
    '------------------/


Sub TestMyClass1()
   
    Class1.Show
    End Sub
    '------------------/

O ile w przypadku TestMyForm oczywiście bez problemu pokaże nam się Userform, to w przypadku TestMyClass1 dostaniemy błąd:
VBE napisał/a:
Compile error: Variable not defined

i wskazanie na Class1. Zeby wszystko działało musimy powołać obiekt klasy Class1 i metodę .Show wywołać na tym obiekcie:
Kod:
Sub TestMyClass1()
   
    Dim cls As New Class1
        cls.Show
    End Sub
    '------------------/

I tym razem otrzymujemy na ekran przygotowany MsgBox. Reasumując: w VBA (może w całym VB6?) nie ma obsługi metod statycznych, więc zawsze wywołując metodę wywołujemy ją na stworzonym obiekcie.
Niezorientowany wędrowiec napisał/a:
Dlaczego zatem metoda .Show działa na klasie Userform a na zwykłej klasie nie i trzeba najpierw powołać obiekt klasy class1?

__Wyjaśnienie [laika dla laika]__
W VBA istnieje coś takiego jak możliwość instancji domyślnej, czyli zawsze do dyspozycji mamy obiekt tej klasy, nawet bez jego powoływania. Co więcej takiego obiektu nie możemy trwale zniszczyć go przez Set Nothing, bowiem automatycznie bez naszej ingerencji tworzony jest na nowo.
Zaciekawiony wannabe Pro koder napisał/a:
Jak więc ustawić naszą klasę, żeby zachowywała się identycznie jak Userform w tym zakresie?

__Nasz hack/trick______
Odpowiada za to tajemniczy tytułowy atrybut klasy VB_PredeclaredId. Niestety bezpośrednio z edytora VBEditor nie mamy możliwości jego modyfikacji. Wyeksportujmy naszą klasę class1 (prawoklik na klasie i Export File) i "formę" Userform1 odpowiednio do plików class1.cls i UserForm1.frm. Pliki te możemy otworzyć edytorem tekstu, np notatnikiem i wówczas zauważymy w nagłówku coś podobnego:
Cytat:
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Class1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False

Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False

W pliku UserForm1 będzie kilka różnic w nagłówku, ale zwróćmy uwagę na interesujący nas atrybut PredeclaredID:
Cytat:
Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} UserForm1
Caption = "UserForm1"
ClientHeight = 3030
ClientLeft = 120
ClientTop = 450
ClientWidth = 4560
OleObjectBlob = "UserForm1.frx":0000
StartUpPosition = 1 'CenterOwner
End
Attribute VB_Name = "UserForm1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False

Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False

Zamieńmy więc w naszym pliku klasy class1 atrybut Attribute VB_PredeclaredId z false na true, zapiszmy naszym edytorem (u mnie notatnik). Następnie usuwamy klasę class1 w naszym pliku i importujmy do naszego skoroszytu:
(w VBE: File>Import File>Class1.cls)

Nasze początkowe makro z przykładu w module1
Kod:
Sub TestMyClass1()
   
    Class1.Show
    End Sub

ożyło i VBE już nie zwraca błędu kompilacji, działa tak samo jak UserForm.

__Ok... Ale po co te kombinacje?__
Wyobraźmy sobie, że mamy klasę do obsługi ścieżek myPath z metodą do budowania pełnej ścieżki ze ścieżki względnej:
Kod:
' class1
Public Function BuildPathFromRelative(ByVal relativePath As String)
   
    With Application
        BuildFullPathFromRelative = .ThisWorkbook.path & _
                                .PathSeparator & _
                                 relativePath
    End With
    End Function

Metoda prosta i przydatna, ale jej wywołanie nie jest wygodne w tradycyjny sposób:
Kod:
' module1
Sub test2()
   
    Dim myNewPath As String
    With New myPath
        myNewPath = .BuildFullPathFromRelative ("Foo")
    End With
    End Sub
    '------------------/

lub
Kod:
' module1
Sub test3()
   
    Dim objPath As New myPath
    Dim myNewPath
        myNewPath = objPath.BuildFullPathFromRelative("Foo")
    End Sub
    '------------------/

Faktycznie, żadne ułatwienie. Ale gry zastosujemy nasz sposób z zamianą wspomnianego atrybutu z false na true, wówczas zaczniemy doceniać:
Kod:
' module1
Sub test4()
   
    Dim myNewPath As String
        myNewPath = myPath.BuildFullPathFromRelative("Foo")
    End Sub
    '------------------/

Najlepsze jest to, że wpisując Build ... i CTRL+Spacja nie uzupełni nam się cała metoda BuildFullPathFromRelative przez Intellisense- metoda jest enkapsulowana czy zamknięta przed dostępem, światem. Dopiero pokazuje nam się po wpisaniu myPath., dzięki temu w przypadku większych zestawów funkcji (metod) usprawniających nam pracę nie narobimy sobie bałaganu. Tworzymy wiele klas i nie dbamy nawet o unikalność nazw. Daje to ogromne możliwości budowania całego zestawu łatwo dostepnych funckji łatających braki VBA i usprawniających nasz kod.
ID posta: 391736 Skopiuj do schowka
 
 
Rafał B.
Stały bywalec Excelforum



Wersja: Win Office 2016
Pomógł: 36 razy
Posty: 253
Wysłany: 06-09-2020, 07:49   

__DOPOWIEDZENIE __
Ten końcowy argument o "schowaniu" może nie do wszystkich przemawiać, więc w paru słowach go rozwinę. Nawet wśród wieloletnich zaawansowanych koderów VBA istnieje wielu z przeświadczeniem, że nie można stosować dwóch publicznych procedur/funkcji o takiej samej nazwie. To oczywiście nieprawda, stwórzmy 2 nowe moduły moduleA i moduleB:

Kod:
' moduleA
Public Sub Hello()
   
    MsgBox "ModuleA"
    End Sub
    '------------------/

i w drugim:
Kod:
' moduleB
Public Sub Hello()
   
    MsgBox "ModuleB"
    End Sub
    '------------------/

W głównym module module1:
Kod:
Public Sub Test5()

   ' test pierwszy
    Hello   '<~~ błąd

   ' test drugi
    ModuleA.Hello

   ' test trzeci
    ModuleB.Hello
    End Sub
    '------------------/

Tylko test pierwszy zwróci znany nam wszystkim błąd
VBE napisał/a:
Compile error: Ambiguous name detected: Hello

Natomiast po zakomentowaniu tej linii test drugi i trzeci zadziałają bez problemu- mówimy bowiem VBE dokładnie jakiej procedury ma używać. Wydaje się to wygodne, i faktycznie ma swoje pewne zastosowanie w świecie VBA (w tzw. fabrykach Factory). Ale dla budowania zestawu funkcji naszej własnej biblioteki nie jest to najlepsze wyjście- po stworzeniu kilku modułów zaczniemy otrzymywać w VBE całą masę śmieciowych podpowiedzi intellisense, lepiej więc nasze funkcje-metody udostępniać tylko obiektom własnej klasy, choćby to miał być obiekt domyślnej instancji, jak w przedstawianym temacie.

Taki przykład przyszedł mi do głowy- tutaj na forum Artik poruszył temat problemów z Application.Transpose (powyżej 65k wierszy gubi dane). Zaimplementować rozwiązanie- również zaproponowane przez Artika można głownie na 2 sposoby: UDFka lub metoda w klasie.
Kod:
' Module1
Public Function Transpose(ByVal arr As Variant)
   
    ' implementacja
    ' np. kod Artika
    End Function


Sub wariant1()

    ' z podatnością na błąd
    x = Application.Transpose(arr)
   
    ' bez podatności - nasza nowa funkcja
    x = Transpose(arr)
   
    End Sub
    '------------------/


Wariant drugi - klasa np xAppliaction
Kod:
' klasa xApplication
Public Function Transpose(ByVal arr As Variant)
   
    ' implementacja
    ' np. kod Artika
    End Function
    '------------------/

i w zwykłym module użycie

Kod:
'Module1
Sub wariant2()

    ' z podatnością na błąd
    x = Application.Transpose(arr)
   
    ' bez podatności - nasza nowa funkcja
    With New xApplication
        x = .Transpose(arr)
    End With
   
    ' lub inaczej
    Dim cls As New xApplication
        x = cls.Transpose(arr)
    Set cls =Nothing
    End Sub
    '------------------/


W wariancie drugim, jeśli zastosujemy zmianę atrybutu PredeclaredId w klasie xApplication, wówczas użyjemy naszej funkcji, a w zasadzie już metody:
Kod:
Sub wariant()

    ' z podatnością na błąd
    x = Application.Transpose(arr)
   
    ' bez podatności - nasza nowa funkcja
    x = xApplication.Transpose(arr)
   
    End Sub
    '------------------/

łączymy więc zalety obu rozwiązań, które raz jeszcze wypiszę:
:arrow: z jednej strony nasza metoda Transpose nie "lata" po całym projekcie i jest dostępna jedynie z "pojemnika- klasy" xApplication, po wpisaniu Tran i CTRL+Spacja* nic nam VBE nie uzupełni, bo dopóki nie wpiszemy xApplication. z kropeczką - tej metody nie widzi
:arrow: z drugiej nie martwimy się o irytującą potrzebę powoływania na nowo obiektów do trywialnych zadań, korzystamy z tego domyślnego obiektu, podstawiając mu tylko dane.
:arrow: Dodatkowo mamy możliwość użycia With jeśli chcemy wykonać kilka operacji, czego tak przez wielu ukochana UDFka nie umożliwi.

Rozwiązanie przedstawione w temacie ma dwie znane mi "wady"
:arrow: Pierwsza wada- obciąża pamięć trzymając domyślny obiekt cały czas w pamięci. Więc jeśli piszesz oprogramowanie w VBA dla lotów kosmicznych SpaceX mając do dyspozycji 128kB pamieci RAM- powinieneś tego rozwiązania unikać.
:arrow: Druga wada to teorie niektórych microsoftowych Most Valuable Professionals o braku kontroli nad (chwilowym) niszczeniem tego domyślnego obiektu. Ale to już temat, który już wyraźnie nawiązuje do sporu .Unload vs .Hide, też w wolnej chwili napiszę parę słów, jak ja to rozumiem okiem nie-programisty.

* w poprzednim poście powinno być własnie na końcu CTRL+Spacja zamiast CTRL + Enter, ale już nie mogę edytować niestety: Najlepsze jest to, że wpisując Build ... i CTRL+Enter
-------------------------------------------------------------------
Wszelkie uwagi mile widziane, przede wszystkim krytyczne :-)
ID posta: 391741 Skopiuj do schowka
 
 
Wyświetl posty z ostatnich:   
Odpowiedz do tematu
Nie możesz pisać nowych tematów
Nie możesz odpowiadać w tematach
Nie możesz zmieniać swoich postów
Nie możesz usuwać swoich postów
Nie możesz głosować w ankietach
Nie możesz załączać plików na tym forum
Możesz ściągać załączniki na tym forum
Dodaj temat do Ulubionych
Wersja do druku

Skocz do:  

Powered by phpBB modified by Przemo © 2003 phpBB Group
Theme xandgreen created by spleen& Programosy modified v0.3 by warna
Opieka techniczna www.marketingNET.pl

Archiwum

Strona używa plików cookies.

Kliknij tutaj, żeby dowiedzieć się jaki jest cel używania cookies oraz jak zmienić ustawienia cookie w przeglądarce.
Korzystając ze strony użytkownik wyraża zgodę na używanie plików cookies, zgodnie z bieżącymi ustawieniami przeglądarki.
Sprawdź, w jaki sposób przetwarzamy dane osobowe