ID tematu: 68845
 |
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
|
|
|
 |
|
|
|
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ę:
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
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.
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"
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ć.
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
|
|
|
 |
|
|
|
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
|
 |
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
|