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: 73468 Skopiuj do schowka Tymczasowe ukrywanie formuł
Autor Wiadomość
Maciej Gonet 
Excel Expert


Wersja: Win Office 365
Pomógł: 3616 razy
Posty: 10635
Wysłany: 09-10-2022, 01:22   Tymczasowe ukrywanie formuł

Czasem dobrze by było mieć w tej samej komórce wartość stałą i formułę.

Może to odnosić się do sytuacji, gdy normalnie korzystamy z wartości typowej obliczonej formułą, ale w pewnych okolicznościach chcemy mieć możliwość ręcznej korekty tej wartości.
Bywa też tak, że formuły są złożone i chcemy na pewien czas zrezygnować z ich aktualizacji (przeliczania), ale nie w całym arkuszu, tylko w wybranych komórkach.
Innym razem w komórce odliczamy czas i chcemy ten stoper na chwilę zatrzymać, ale z możliwością kontynuacji.

Osiągnięcie takich celów wprost nie jest możliwe, bo zawsze wprowadzenie stałej do komórki nadpisuje formułę, jeśli tam wcześniej była. Można jednak wykorzystać pewne sposoby, aby przechować formułę z komórki przez pewien czas w innym miejscu, a później odtworzyć ją, gdy zajdzie potrzeba.

Znam cztery takie sposoby, ale każdy z nich ma jakieś wady. Pewnie znalazłoby się i więcej bardziej wyrafinowanych pomysłów, ale liczę, że i te cztery mogą być przydatne w sprzyjających okolicznościach.

Dokładniejszy opis i proste przykłady w załączonych plikach.

Tymczasowe ukrywanie formuł.docx
Pobierz Plik ściągnięto 167 raz(y) 23.05 KB

Ukrywanie formuł.xlsm
Pobierz Plik ściągnięto 154 raz(y) 32.05 KB

ID posta: 421121 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 09-10-2022, 12:00   

Czy analizował Kolega możliwość dopisania customego węzła do XML? Wydaje się, że to powinno się udać. W przypadku sukcesu nie wiem jak ewentualnie z utrzymaniem tych danych w binarnym .xlsb
_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
ID posta: 421122 Skopiuj do schowka
 
 
Maciej Gonet 
Excel Expert


Wersja: Win Office 365
Pomógł: 3616 razy
Posty: 10635
Wysłany: 09-10-2022, 15:28   

Obawiam się, że na te pytania nie potrafię odpowiedzieć. W ogóle nie bardzo rozumiem, o co pytasz. Ja nie jestem profesjonalnym informatykiem, zajmowałem się Excelem o tyle, o ile było mi to potrzebne w pracy. XML nie był obiektem mojego zainteresowania, również z plików .xlsb korzystam sporadycznie i nigdy nie analizowałem niuansów związanych z tymi plikami.

Napisałem to, co gdzieś znalazłem, sprawdziłem, zebrałem i przystosowałem do swoich potrzeb i tyle. Każdy może to teraz dalej testować i oswajać według woli i potrzeb. A ja nie mam wiele więcej do dodania, chyba że byłyby jakieś sprawy wymagające bardziej szczegółowego wyjaśnienia.
ID posta: 421125 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 10-10-2022, 00:28   

Nie chodziło mi o żadne wymyślne koncepty, ale faktycznie może napisałem w sposób niejasny.

Wiadomo, że upraszczając .xlsx i .xlsm są zestawami plików xml. I potencjalnie można spróbować do takiego XMLa dodać własny węzeł (o ile nie ma jakiejś wyrafinowanej walidacji przez Excela) lub twórcy nawet może przewidzieli jakiś klucz, gdzie można customowe dane dodać(?). Mamy bezpośredni dostęp do takiej formy poprzez właściwość Value z parametrem 11 (enumka XlRangeValueDataType.xlRangeValueXMLSpreadsheet) co m.in. poruszyłem w tym wątku całkiem niedawno https://www.excelforum.pl...lue-vt72373.htm

Co do pytania o .xlsb, to oczywiście format własny Excela, binarny, więc potencjalnie przy konwersji .xlsm do .xlsb takie "nadmiarowe" dane mogą ulegać usunięciu.

Ale z odpowiedzi Kolegi rozumiem, że temat otwarty pod tym względem; w wolnej chwili spróbuję (chyba, że mnie ktoś uprzedzi i powiadomi o wyniku na forum), bo takie potencjalne rozwiązanie byłoby czystsze i ogólniej nieco lepsze od zaproponowanych w tym wątku, bo nie zabierałoby żadnej funkcjonalności. No chyba że przeoczyliśmy jakąś właściwość nie typu `read-only` obiektu Range i da się prościej :-D
_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
ID posta: 421133 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 29-12-2023, 01:54   

Dawno nie miałem okazji pisać czegoś ambitniejszego w VBA poza tzw. utrzymywaniem kodu i małymi przeróbkami, więc korzystając z przerwy świątecznej coś tam nareszcie wykodziłem nowego.

W temacie wątka okazało się, że intuicja i tym razem mnie nie zawiodła: skoro współczesny plik excela jest de facto paczką XML, to przypuszczałem, że w VBA przewidziano możliwość dopisania swoich węzłów do tego dokumentu. Zamiast przekopywać dokumentację, zapytałem model GPT4 i raczył był mi zasugerować użycie nieznanej mi kompletnie wcześniej metody `Workbook.CustomXMLParts.Add`.

Niestety chyba (?) nie ma możliwości przyporządkowania takiego węzła bezpośrednio do XML obiektu range, co chwilowo ostudziło moją euforię. Ale z powodu pilnej potrzeby opracowania rozwiązania optymalizacyjnego (włączanie/wyłączanie kalkulacji nie wchodziło w rachubę) powróciłem do tematu i tak powstało rozwiązanie oparte na dwóch napisanych przeze mnie klasach.

Podstawą jest obiekt klasy CustomXmlData. Pomaga w dodawaniu danych do XML skoroszytu. Zamiast opisu zacytuję przykład użycia z załączonego pliku:

Kod:

' CustomXmlData - przykład wykorzystania klasy do zapisu danych do skoroszytu
' bez użycia żadnych dzieci klasy Worksheet jak np. Range


''
' Przykład - zapis
'
Public Sub Example0PrepareTestCase()
   
    With CustomXmlData
       
      ' simple key-value data
        .Add data:="4.34", id:="RateEUR", namespace:="BuyRate"
        .Add data:="3.94", id:="RateUSD", namespace:="BuyRate"
        .Add data:="4.00", id:="RateUSD", namespace:="SellRate"
         
        ' przy braku przestrzeni nazw, będzie użyta przestrzeń domyślna
        .Add data:="Titelitury", id:="fancyName"
             
      ' 2D array
        .Add data:=Array( _
                Array("name", "Jan"), _
                Array("lastname", "Iksiński"), _
                Array("age", "62")), _
             id:="p0187", _
             namespace:="personalData"
    End With
    End Sub


''
' Przykład - odczyt
'
Public Sub Example0ReadSimpleData()

    With CustomXmlData
       
      ' odczyt prostych wartości:
     
        MsgBox .GetValueById(id:="fancyName")
        ' alternatywny sposób:
        ' MsgBox .GetDataAsCollection(id:="fancyName")("value")

        MsgBox .GetValueById(id:="RateUSD", namespace:="SellRate")
        ' alternatywny sposób:
        ' .GetDataAsCollection(id:="RateUSD", namespace:="SellRate")("value")

        MsgBox "Wiek zanonimizowanowego pacjenta id=`p0187` wynosi " & _
            .GetValuesById(id:="p0187", namespace:="personalData")("age") & _
            " lat(a)"
           
        ' przykład usunięcie wartości
        .Delete id:="fancyName"
        MsgBox .GetValueById(id:="fancyName")    ' rzuca wyjątek
       
    End With
    End Sub
_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
Ostatnio zmieniony przez Rafał B. 29-12-2023, 02:08, w całości zmieniany 2 razy  
ID posta: 432431 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 29-12-2023, 01:58   

I tutaj wchodzi już docelowe rozwiązanie: na tę klasę CustomXmlData utworzyłem wrapper w postaci klasy RangeFormula, która już wykonuje właściwe zadanie- czyli ułatwia przechowanie i zapis formuł do arkusza. Przykład użycia w kontekście tabeli:

Kod:

'''
' Odpalane jednorazowo przy działającej formule.
' Zapisze istniejącą formułę z pierwszej komórki podanego zakresu,
' którą potem będzie można odtworzyć.
'
Public Sub Test2PrepareTestCase()
   
    ' "test_suma" i  "test_potega" to pomocnicze nazwy, można je pominąć
    ' (wówczas do uruchamiania będzie potrzeba
    ' przy użyciu podawać obiekt Range)

    With TestSheet2.ListObjects(1)

        RangeFormula.SaveByRange .ListColumns("suma").DataBodyRange, , "test_suma"

        RangeFormula.SaveByRange .ListColumns("potega").DataBodyRange, , "test_potega"

    End With
    End Sub
   
   
'''
'
'
Public Sub Test2values()

    With RangeFormula
        .ApplyValues "test_suma"
        .ApplyValues "test_potega"
    End With
    End Sub
   

'''
'
'
Public Sub Test2formulas()

    With RangeFormula
        .ApplyFormulas "test_suma"
        .ApplyFormulas "test_potega"
    End With
    End Sub
   


Reasumując nie ma potrzeby operowania na klasie CustomXmlData, jedynie RangeFormula (ale obie muszę być dołączone w projekcie). Te klasy rozdzieliłem głównie z powodu bałaganu jaki powstał przy braku ich separacji.

To przesłane rozwiązanie to na razie szkic pisany dość szybko "na kolanie" i zawiera kilka mniej lub bardziej poważnych spraw do poprawy, które zazwyczaj opisałem gdzieś tam w formie TODO w komentarzach.
Na pewno wymagałoby także optymalizacji, moze też zawierać jakieś "śmieci" ze starszych wersji, którcyh nie wychwyciłem, a które powinny zostać usunięte.

Zadanie początkowo proste okazało się mieć wiele pułapek, stąd koncepcja podczas pisania zmieniała się kilkukrotnie i stąd brak pewnej elegancji. Kod jest "niepudrowany", więc puryści jak np. master_mix :mrgreen: może być zdegustowany prostym wykorzystaniem w klasie RangeFormula innej klasy (CustomXmlData), gdzie oczywiście powinien być zastosowany jako warstwa pośrednia interfejs eksponujący metody. Mam świadomość wielu mankamentów, po prostu takie rozwiązania można poprawiać w nieskończoność, a chciałem już jakiś zarys zaprezentować i udowodnić na zasadzie proof of concept od pomysłu do realizacji, że idąc tą drogą można wyeliminować w zasadzie wszystkie poważne wady innych rozwiązań w temacie przechowywania formuł.

RangeFormulaClass.xlsm
Pobierz Plik ściągnięto 71 raz(y) 95.85 KB

_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
ID posta: 432432 Skopiuj do schowka
 
 
Maciej Gonet 
Excel Expert


Wersja: Win Office 365
Pomógł: 3616 razy
Posty: 10635
Wysłany: 29-12-2023, 02:23   

Bardzo dziękuję za szczegółowe przedstawienie swojego pomysłu i koncepcji rozwiązania. Jest to już jednak dość wysoki poziom i trzeba się temu przyjrzeć z lekką głową.
Będę miał o czym myśleć w Nowy Rok.

A tymczasem życzę Tobie i nam wszystkim, żebyś częściej tu do nas zaglądał, póki Forum jeszcze istnieje. Zawsze można się czegoś ciekawego od Ciebie dowiedzieć.
ID posta: 432433 Skopiuj do schowka
 
 
master_mix 
Excel Expert



Wersja: Win Office 365
Pomógł: 1293 razy
Posty: 2639
Wysłany: 30-12-2023, 22:22   

Rafał B. miło Cie widzieć , już się martwiłem że utknąłeś pod stertą pieluch i się szybko nie pokażesz, a ludzi z ciętym językiem mało na tym forum :mrgreen:

Ciekawy przykład i ciekawe podejście.
Już kiedyś rozważałem wykorzystanie tych nowych klas z serii CustomXML..., które głownie dla zastosowań VSTO się pojawiły, ale myślałem pod kątem zapamiętywania ostatnich wpisywanych wartości - temat porzuciłem, trochę mnie znudził a trochę lipa z tym zapisywaniem binarnym.
Znam sporo osób które notorycznie zapisują xlsb, lub nawet xls. - Edit: W xlsb działa wszystko prawidłowo, jednak jak ktoś zapisze xls, to lipa. Na szczęście już rzadkość ...

Klasy ciekawe i pożyteczne, zastosowań można znaleźć pewnie jeszcze kilka. Sporo metod własności i zdarzeń do wykorzystania.

Rafał B. napisał/a:
master_mix może być zdegustowany prostym wykorzystaniem w klasie RangeFormula

Nie , no przestań :roll:
niechlujny kod to moja specjalność, innych tylko pouczam, czyli bardziej hipokryta niż purysta :mrgreen:

Kod całkiem zgrabny i widać że się napracowałeś :thumbup:
Masz rację że takiego typu projekty można poprawiać w nieskończoność, właściwie każde projekty ...

Czepię się oczywiście czegoś:
sprawa jeden:
Jak wiesz nie jestem zwolennikiem "wymuszania" klas statycznych w VB, po to język VB, VB.NET mają moduły, których inne języki-platformy nie mają i tylko dlatego w innych językach są klasy statyczne. Ale to jak kiedyś wyjaśniliśmy sprawa poglądów religijnych :)

Sprawa dwa:
Projektując taki model danych, zakładamy że wykorzysta go jakiś inny programista i wcale nie musi on być bardzo zaawansowany w pisaniu, co za tym idzie wykorzystanie Twoich klas jest nieco skomplikowane i może być potraktowane mniej atrakcyjnie.

Sprawa trzy:
Myślę że niepotrzebnie ładujesz cały dokument do klas z biblioteki MS XML a poźniej nadpisujesz cały plik customowy, przecież masz do niego dostęp przez klasy office.CustomXML...

sprawa czterty:
Zapisywałbym customowe XML jako 1:1 => arkusz:XML

coś takiego:
Kod:
<rangeData xmlns="Arkusz1">
<range xmlns="A1" xmlns:ns2="formula" xmlns:ns3="value" ns2:formula="=D1" ns3:value="450">A1</range>
<range xmlns="A2" xmlns:ns2="formula" xmlns:ns3="value" ns2:formula="=E2" ns3:value="620">A2</range>
<range xmlns="A3" xmlns:ns2="formula" xmlns:ns3="value" ns2:formula="=G3*E2" ns3:value="483600">A3</range>
</rangeData>


Moja propozycja jest taka (moduł standardowy - można przerobić na klasę):
Kod:
Public Type PropertyRange
    value As Variant
    formula As Variant
End Type

Public Enum ReadWrite
    eRead = 0
    eWrite = 1
End Enum

Private exRange As Excel.Range
Private myWorkbook As Excel.Workbook
Private myWorksheet As Excel.Worksheet

Public Function ExtendedRange(ByVal eRange As Excel.Range, _
        Optional ByVal eReadWrite As ReadWrite = 0) As PropertyRange
   
    Set exRange = eRange(1)
    Set myWorksheet = eRange.Parent
    Set myWorkbook = myWorksheet.Parent
   
    Dim cPart As Office.customXMLPart
    Set cPart = GetCustomXMLPart
    If cPart Is Nothing Then
        If eReadWrite = eRead Then
            Exit Function
        Else
            Set cPart = CreateCustomXMLPart
        End If
    End If
   
    Dim nd As Office.CustomXMLNode
    Set nd = GetCustomXMLNode(cPart)
    If nd Is Nothing Then
        If eReadWrite = eRead Then
            Exit Function
        Else
            Set nd = CreateCustomXMLNode(cPart)
            ExtendedRange = GetAttributeNode(nd)
        End If
    Else
        If eReadWrite = eRead Then
            ExtendedRange = GetAttributeNode(nd)
        Else
            ExtendedRange = ReplaceAttributeNode(nd)
        End If
    End If
   
End Function

Private Function GetCustomXMLPart() As Office.customXMLPart
    On Error Resume Next
    Set GetCustomXMLPart = myWorkbook.CustomXMLParts.SelectByNamespace(myWorksheet.CodeName)(1)
End Function

Private Function GetCustomXMLNode(ByVal cXMLPart As Office.customXMLPart) As Office.CustomXMLNode
    Dim rngAddress As String
    Dim xpathQuery As String
    rngAddress = exRange.address(False, False)
    xpathQuery = "//*[namespace-uri()='" & rngAddress & "']"
    Set GetCustomXMLNode = cXMLPart.SelectSingleNode(xpathQuery)
End Function

Private Function CreateCustomXMLPart() As Office.customXMLPart
    Dim mMap As String
    mMap = _
        "<rangeData xmlns =""" & myWorksheet.CodeName & """ />"
    Set CreateCustomXMLPart = myWorkbook.CustomXMLParts.Add(mMap)
End Function

Private Function CreateCustomXMLNode(ByVal cXMLPart As Office.customXMLPart) As Office.CustomXMLNode
    Dim rngAddress As String
    rngAddress = exRange.address(False, False)
    cXMLPart.DocumentElement.AppendChildNode "range", rngAddress, msoCustomXMLNodeElement, rngAddress
    Set CreateCustomXMLNode = cXMLPart.DocumentElement.LastChild
    cXMLPart.addNode CreateCustomXMLNode, "formula", "formula", , msoCustomXMLNodeAttribute, exRange.FormulaLocal
    cXMLPart.addNode CreateCustomXMLNode, "value", "value", , msoCustomXMLNodeAttribute, exRange.value
End Function

Private Function GetAttributeNode(ByVal node As Office.CustomXMLNode) As PropertyRange
    Dim att
    Dim retPropertyRange As PropertyRange
    For Each att In node.Attributes
        Select Case att.BaseName
            Case "value"
                retPropertyRange.value = att.NodeValue
            Case "formula"
                retPropertyRange.formula = att.NodeValue
        End Select
    Next
    GetAttributeNode = retPropertyRange
End Function

Private Function ReplaceAttributeNode(ByVal node As Office.CustomXMLNode) As PropertyRange
    Dim att
    Dim retPropertyRange As PropertyRange
    For Each att In node.Attributes
        Select Case att.BaseName
            Case "value"
                att.NodeValue = exRange.value
                retPropertyRange.value = att.NodeValue
            Case "formula"
                att.NodeValue = exRange.FormulaLocal
                retPropertyRange.formula = att.NodeValue
        End Select
    Next
    ReplaceAttributeNode = retPropertyRange
End Function


Praktycznie jedna prosta publiczna metoda ogarnia temat.
Łatwy w implementacji nawet dla niedoświadczonych. Patrz *jpg

Test:
Kod:
Sub WykorzystajDoWatku_ExForum()
   
    With Arkusz1
        'zapisz Rnage:A1 - lub w pętli większy zakres
        ExtendedRange .Range("A1"), eWrite
       
        'użyj wartosci
        .Range("A1").value = ExtendedRange(.Range("A1")).value
       
        Stop 'luknij czy ok
       
        ''użyj formuły
        .Range("A1").FormulaLocal = ExtendedRange(.Range("A1")).formula
    End With
   
End Sub


przykład w załączniku.

EDIT:
jeszcze dorzuciłbym bym publiczną metodę do sprawdzenia czy Range zapisane w xml
czyli będą 2 publicznie łącznie
Kod:
Public Function IsSavedRange(ByVal eRange As Excel.Range) As Boolean
    Set exRange = eRange(1)
    Set myWorksheet = eRange.Parent
    Set myWorkbook = myWorksheet.Parent
    Dim rngAddress As String
    rngAddress = exRange.address(False, False)
   
    Dim cPart As Office.customXMLPart
    Dim nd As Office.CustomXMLNode
    Set cPart = GetCustomXMLPart
    If cPart Is Nothing Then
        IsSavedRange = False
        Exit Function
    Else
        Set nd = GetCustomXMLNode(cPart)
        If nd Is Nothing Then
            IsSavedRange = False
            Exit Function
        End If
    End If
    IsSavedRange = True
End Function


scr1.jpeg
Plik ściągnięto 8 raz(y) 13.52 KB

scr2.jpeg
Plik ściągnięto 8 raz(y) 17.96 KB

scr3.jpeg
Plik ściągnięto 4 raz(y) 22.77 KB

scr4.jpeg
Plik ściągnięto 8 raz(y) 36.33 KB

RangeFormulaClass m_mModule.xlsm
Pobierz Plik ściągnięto 66 raz(y) 33.05 KB

_________________

Podejmę współpracę (pracę)
Programowanie C#, Android, iOS, VB.NET, VBA, ASP.NET Core, WPF, Xamarin, Power Platforms, XAML, MVC, LINQ, Entity Framework. Bazy danych SQL Server, Oracle, MySQL, Firebird
Wrocław i okolice …lub zdalnie.
ID posta: 432444 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 31-12-2023, 15:15   

Dzięki za wskazówki i spostrzeżenia! :danke

Ad 1. Co do klas statycznych, to faktycznie ciężko mnie przekonać do zmiany na moduły ze względu na a'la enkapsulację i Intellisense; nie lubię jak w wielkim projekcie IDE podpowiada mi tysiące zbędnych funkcji w złym kontekście, gdzie powinno podpowiadać tylko standardowe, przez co spowalnia mi to pisanie kodu.
Stosuję klasy statyczne w zasadzie tylko do narzędzi, które nie mają swojego stanu (pól). Wielu uznaje to za `code smell`, ale moja praktyka akurat nie podziela obaw co do tego zagadnienia. Modułów używam głównie do skryptów, które korzystają oczywiście ze stosownych klas.

Z wad klas statycznych (i ogólnie klas) widzę tylko jedną i jest to błąd projektowy VBA (może VB6 też?), że brak public const i public enum. Stałe i enumy nie są eksponowane i dostępne bezpośrednio (bez żadnych Property Get i powoływania instancji). Jako obejście załączam stałe/enumy zwykle w zwykłym module, stosując dobre nazewnictwo.

Ad 2.
Pisząc te klasy starałem się mieć to na uwadze, żeby ktoś mógł skorzystać z owoców mojej pracy. Skoro zwróciłeś uwagę, że ich stosowanie jest dość trudne, to jednak chyba na tym polu niestety poległem., chociaż to był jeden z moich głównych celów- mało metod, wygoda i ekspresywne nazwy.

Co do pozostałych punktów to zawierają cenne wskazówki, ale muszę zrozumieć o co chodzi, przysiądę do tego jeszcze po Nowym Roku. MSXML stosowałem tylko do parsowania węzła, nie wiedziałem że można inaczej pomijając manipulacje stringami. Nie użylem worksheet.codename, bo chciałem pierwotnie, by można było używać klasy na innym pliku niż bieżący (poza ThisWorkbook). Alle po zastanowieniu to chyba byłby niepotrzebny overkill z mojej strony i ciężko wyobrazić sobie taki scenariusz w praktyce.


PS
Co do mojego kodu- jakby ktoś testował- to wymagany jest hotfix (metoda getFullAdress powinna otaczać apostrofami nazwę arkusza, żeby nie rzucało wyjątkiem przy arkuszach, których nazwa zawiera spację).

Poza tym rozwiązanie w tej wersji "beta" działa produkcyjnie zadziwiająco dobrze i bezproblemowo: na arkuszach bardzo obciążonych formułami, które nie wymagają częstego odświeżania, cały kod na zdarzenie Activate
Kod:

With RangeFormula

   .ApplyFormulas (...)

   Me.Calculate

   .ApplyValues (...)

End With


Ale ulepszę klasy za kilka dni stosując wybrane wskazówki od master_mix i ewentualnie innych użytkowników, którzy podzielą się swoimi spotrzeżeniami, bo kilka fajnych wskazówek już dostałem.

PPS
co do ilości metod, to raczej podejście stosowałbym w myśl upraszczania rzeczy jak się da, ale nie bardziej. Zamiast Do(Byval AorB as something) lepsze jest DoA() i DoB(). Łatwiej taki kod przetestować, jest czytelniejszy i łatwiejszy w stosowaniu. Ale może jestem za bardzo pod wpływem Wujka Boba po przeczytaniu słynnego Czystego Kodu :mrgreen:

Po raz któryś brakuje na forum opcji edycji postów po 30 minutach, w jednym poście mógłbym aktualizować wersję rozwiązania. Dla fioletowych można byłoby rozważyć taki przywilej... ;-)
_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
ID posta: 432452 Skopiuj do schowka
 
 
master_mix 
Excel Expert



Wersja: Win Office 365
Pomógł: 1293 razy
Posty: 2639
Wysłany: 02-01-2024, 16:23   

Pisałem tego posta nad ranem w nowy rok , i już kończyłem, kiedy wywaliło forum w kosmos ...
Nie byłem pijany i na pewno to nie moja wina :D
Więc piszę od nowa.

Rafał B. napisał/a:
Z wad klas statycznych (i ogólnie klas) widzę tylko jedną i jest to błąd projektowy VBA (może VB6 też?), że brak public const i public enum

Enumy publiczne można śmiało stosować.
Const, Type (struktury), już nie, ale lepiej utworzyć osobną klasę którą można wykorzystać wyłącznie jako pola.

Rafał B. napisał/a:
ciężko mnie przekonać do zmiany na moduły ze względu na a'la enkapsulację i Intellisense

intellisense jest też i dla modułu:
Kod:

With NazwaModulu
.Podpowiadana metoda
End With

Jeżeli chcesz koniecznie bardziej zhermetyzować kod, to używasz 2 klas z publicznymi metodami, a niekoniecznie wszystkie powinny być widocznie,
tym bardziej że się powtarzają, a jest lub będzie potrzeba też wykorzystać metodę z klasy 2 w klasie 1 ale tylko na potrzeby klasy 1 i tym bardziej nie powinna być publiczna.
Wiem że nigdy nie kończy się na jednej klasie, tym bardziej że refaktoryzacja jest ważna.
Jest to nieduży projekt, więc metody publiczne moim zdaniem powinny być tylko w jednej klasie, chociażby po to żeby podczas korzystania z Twojego projektu nie myśleć jaka metoda w klasie 1 i co robi, a jaka w klasie 2, dlaczego "Delete" jest tu i tu ?.

Wiem że VBA dla OOP to mordęga, ale coś się tam da wymodzić.
Można dla tego typu scenariusza wykorzystać efekt "polimorfizmu przez interfejsy".

Robię to w prostych 3 krokach:
1. tworzymy klasę interfejsu InterfaceClass, gdzie deklarujemy tylko puste metody które będą później publiczne.
(możemy tworzyć dowolną ilość interfejsów i implementować wszystkie nawet w tej samej klasie)
Kod:

Public Sub IMetodaSub()
End Sub


2. Teraz tworzę klasę "prywatną" PrivateClass w sensie: będą w niej tylko prywatne metody, ale będziemy mogli jej metody prywatne użyć w klasie publicznej i też ewentualnie z tej klasy publicznej udostępnić.
Po wpisaniu Implements InterfaceClass na początku klasy, pierwsza metoda z klasy interfejsu zostanie automatycznie dodana,
resztę należy dodać standardowo z comboboxów na na górnym pasku edytora VBA, podobnie jak przy Eventach.
Automatycznie też do nazwy metody z interfejsu doda się nazwa klasy z której implementujemy (nazwa interfejsu).
WAŻNE - wszystkie metody z interfejsu muszą zostać zaimplementowane, inaczej kompilator zgłosi błąd. Uzupełnianie ciała metod nie jest wymagane we wszystkich, ale jeżeli taka sytuacja nastąpi to znaczy że trzeba refaktoryzować interfejs, czyli rozbić go na mniejsze elementy, bo żle był przemyślany.
Teraz wystarczy dopisać ciała metod.
Kod:

Implements InterfaceClass

Private Sub InterfaceClass_IMetodaSub()
    Debug.Print "Prywatna metoda (Sub)"
End Sub


3. Teraz dodajemy klasę publiczną, która udostępni metody z klasy prywatnej na zewnątrz, lub wykorzysta je tylko dla siebie.
Ja po prostu kopiuje potrzebne metody z klasy interfejsu (usuwam przedrostek "I" - dla lepszej konwencji nazewnictwa) i uzupełniam ciało "polimorficzne"
czyli do interfejsu przypisuję obiekt PrivateClass -> rzutowanie.
PublicClass
Kod:

Private clsPriv As PrivateClass
Private InerFac As InterfaceClass

Public Sub MetodaSub()
    'polimorficzne wykorzystanie interfejsu
    '---------------------------------
    InerFac.IMetodaSub
End Sub

Private Sub Class_Initialize()
    'polimorficzne przypisanie-rzutowanie na interfejs
    'pamiętać należy że rzutowane są tylko te metody z klasy PrivateClass
    'które udostępnia interfejs
    '---------------------------------
    Set clsPriv = New PrivateClass
    Set InerFac = clsPriv
End Sub

Private Sub Class_Terminate()
    Set clsPriv = Nothing
End Sub


Pozostaje nam sprawdzić jak to działa:
Kod:

Sub Test()
    Dim clsPublic As New PublicClass
    clsPublic.MetodaSub

''sprawdzam czy na pewno ukryta jest klasa prywatna:

''kompilator zwróci błąd bo nic z tej klasy nie jest dostępne

    'Dim clsPriv As New PrivateClass
    'clsPriv.IMetodaSub
End Sub


O kilka metod bardziej rozbudowany kod jest w załączniku. Prosty przykład żeby nie komplikować a przekazać koncepcję

Rafał B. napisał/a:
MSXML stosowałem tylko do parsowania węzła, nie wiedziałem że można inaczej pomijając manipulacje stringami.

Uważam że ładowanie do klas MSXML plików xml i zapisywanie, opóźni kod. A skoro wszystko jest dostępne przez klasy Office.CustomPart i inne Office.Custom
i bezpośrednio manipuluje zawartością pliku, to lepiej iść w tą stronę.
Poruszanie się po xml-u zawsze będzie też wydajniejsze przez zapytania-język XPath (XML Path Language).

Co do tego co pisałem 1:1, chodziło mi o to żeby za często nie tworzyć nowego obiektu CustomXMLPart - co za tym idzie nowego pliku w zipie .xlsm
Tylko Tworzyć jeden plik na 1 arkusz, stąd namespace=CodeName, żeby później łatwo odnaleźć plik (CustomXMLPart) i do niego coś dopisać, jeżeli dane dotyczą tego samego arkusza.

Interfejs niewidocznych metod.xlsm
Pobierz Plik ściągnięto 66 raz(y) 26.94 KB

_________________

Podejmę współpracę (pracę)
Programowanie C#, Android, iOS, VB.NET, VBA, ASP.NET Core, WPF, Xamarin, Power Platforms, XAML, MVC, LINQ, Entity Framework. Bazy danych SQL Server, Oracle, MySQL, Firebird
Wrocław i okolice …lub zdalnie.
ID posta: 432468 Skopiuj do schowka
 
 
Rafał B. 
ExcelSpec



Wersja: Win Office 2021
Pomógł: 91 razy
Posty: 524
Wysłany: 02-01-2024, 22:11   

Z enumami to był skrót myślowy- można je stosować, ale jest powszechnie znany związany z nimi bug, który w najmniej spodziewanym momencie pojawi się u klienta, bo pojawia się losowo, czyli niesławne `Compile error: Constant expression required`, dlatego nie stosuję ich w klasach, bo lubię spać spokojnie.

Cytat:
intellisense jest też i dla modułu

to oczywiste, że jest Intellisense, ale chodziło nie o brak Intellisense, tylko za dużo śmieciowych podpowiedzi w Intellisense, pisałem o tym wielokrotnie. Mam projekt na grube kilkadziesiąt tysięcy linii kodu, wiele klas, modułów. Każda nazwa publicznego makra/skryptu zaśmieca mi Intellisense. Wielu to nie przeszkadza, ale mnie pisze się szybciej, gdy mam mniej podpowiedzi do wyboru. Ale poza tym konstrukcja With jest ograniczona do obiektów (i chyba tylko typów), więc do modułu nie da rady bez kombinacji. A lubię ją stosować With, bo dobrze(!) wykorzystana zwiększa przejrzystość i szybkość pisania kodu (nawet czasem szybkośc działania, bo kod trzyma referencję do onbiektu, jak to często robimy z .Range) i brakuje mi jej w innych językach.

Cytat:
Uważam że ładowanie do klas MSXML plików xml i zapisywanie, opóźni kod.

Przecież pisałem, że cenna wskazówka i nie wiedziałem o możliwości uniknięcia tego, więc nie trzeba mnie przekonywać do tej zmiany! :mrgreen:

Natomiast co do przykładu z interfejsami: nie widzę żadnych zalet tworzenia dwóch klas PrivateClass i PublicClass, według mnie lepiej to zrobić w jednej klasie, czyli implementować wszystko w ramach PublicClass odnosząc się do nomenklatury Kolegi. Wszystkto zapewnia deklarowany scope-zasięg widoczności Private/Public metod i akcesorów.
W ogóle w OOP każde powoływanie instancji (w przypadku VBA "new JakaśKlasa") wewnątrz klasy jest podejrzane z gruntu zasad SOLID, bo powinno być wstrzykiwanie zależności. A w tym przykładzie robimy zależność PublicClass korzysta z PrivateClass.

EDYCJA
chyba po namyśle zrozumiałem ideę: ta PublicClass miała być jedna sądząc po eksponowaniu jej metod, a PrivateClass może być potencjalnie wiele (jako poszczególne implementacje) i po zmianie implementacji na np. PrivateClassV2 zmieniamy tylko konstruktor (i typ pola) klasy PublicClass. Czyli de facto jest to ominięcie wady jezyka, że interfejs nie może implementować innych interfejsów i PublicClass pełni tę rolę "superinterfejsu".

Jeśli tak, to ja wolę jednak utworzenie interfejsu odpowiadającego od razu wymaganiom (czyli dodatkowego łączącego metody InterfaceClass1 i InterfaceClass2). Dodatkowa praca, ale później same benefity. Bo wówczas mamy np. ITaxCalculator i tylko podstawiamy implementacje w jakimś makrze w Module1. Czyli:

Kod:
Dim obj As ITaxCalculator
Set obj = New DefaultTaxCalculator
'Set obj = New NowyLadTaxCalculator ' zmiana kryteriów obliczania podatku
'Set obj = New  JeszczeNowszyLadTaxCalculator  ' zmiana kryteriów obliczania podatku

tax = obj.GetTax(params)
Wówczas moim zdaniem bardziej widoczny jest urok interfejsów, bo w ogóle nie grzebiemy w istniejącym kodzie poza zmianą "new XXXX", jedynie dodajemy implementację i zmieniamy jedną linijkę w głównym makrze. Każdy, kto utrzymywał dużą aplikację, szczególnie w VBA, wie jakim koszmarem jest zmiana wymagań biznesowych klienta. Tydzień temu umawialiśmy się na X jako niezmiennik, a dziś dzwoni i zmieniamy, co przy źle napisanej apce ciągnie lawinę zmian i potencjalnych problemów. Dobre wykorzystanie interfejsów pomaga zmniejszyć ten ból to minimum. A każda zmiana istniejącego kodu, to potencjalnie problem.

Ale co do implementacji, to już kwestie preferencji, nie zbaczajmy z wątku za bardzo :)
_________________
W erze zaawansowanych narzędzi LLM takich jak GPT 50% problemów użytkowników forum może być efektywnie rozwiązanych przez nich samych za pomocą tych narzędzi. Jednak często bardziej cenią swój czas niż czas tych, którzy oferują tutaj pomoc.
ID posta: 432472 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.wip.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