Warsztat 2: Format procentowy - ostateczne rozwiązanie ?

procent.zip
Autor: Krzysztof Naworyta
Baza w formacie MsAccess 97
31kB, 21-02-2003

Opis problemu:

Problem stary jak Access !
Ustawiliśmy w polu tabeli/formularza format procentowy, czy to przy pomocy ogólnej właściwości "procentowy" (Percent),
czy to w sposób bardziej zaawansowany: 0.00#%;-0.00#%[czerwony];0;-
Wszystko wyswietla się prawidłowo, czyli wartość 0.235 wyświetlana jest jako 23,5%, ale samo wprowadzanie danej jest trochę mało intuicyjne.
Mimo ustawionego formatu użytkownik musi wpisywać wartość w formacie ogólnym, czyli: 0.234

Rozwiązanie:

Jedno z pierwszych rozwiązań zaproponował LeS, polegające na triku z "sztucznym" formatem procentowym
LeS zauważył, że jeśli z poziomu procedury Load formularza ustawimy format jako "0.00% " (spacja na końcu), to Access potraktuje te dwa znaczki tak jak każdy inny stały znak w wyrażeniu formatującym, a nie jako komendę do przemnożenia wartości przez 100.
Wada tej metody, to to, że wpisując 23.4 widzimy 23.4%, ale zapisana liczba jest liczbą 23.4, nie zaś 0.234 jak byśmy chcieli przechowywać ją do dalszych obliczeń.

Swoje uwagi do tego rozwiązania sformułował Jacek Kubek:

Oto jego słowa:

Bardziej naturalnym podejściem jest, aby wartość wyświetlana jako 20,14% przechowywana była jako 0,2014 (nie trzeba pamiętać o dzieleniu przez 100 w dalszych obliczeniach).
Dlatego Piotr Lipski zaproponował odwrotne podejście: Pozostawiamy 'naturalny' mechanizm Access'a, a w polu przechowujemy wpisaną liczbę podzieloną przez 100:

Private Sub pole1_AfterUpdate()
    Me!pole1 = Me!pole1 / 100
End Sub
(Propozycja Piotra była szersza, tj. dzieliła przez 100 tylko wartości > 1, lecz powodowało to efekt uboczny: niemożliwość wpisania np. 0,15%, lecz to tutaj nieistotne)

Wszystko pięknie, lecz... jeśli zamiast 20,14 wpiszemy 20,14%, to otrzymamy ... 0,2014%!!! Access ma odwrotny 'naturalny' (wbudowany) mechanizm dzielenia przez 100 działający przy wpisywaniu liczb zakończonych znakiem %, a my dublujemy jego działanie... :-(

Lecz jest proste zabezpieczenie przed taką ewentualnością:
Private Sub pole1_AfterUpdate()
    If Right(Trim(Me!pole1.Text), 1) <> "%" Then
        Me!pole1 = Me!pole1 / 100
    End If
End Sub
I teraz jest pięknie ;-)
Jacek (jacek_kubek@onet.pl)

koniec cytatu

Co temu rozwiązaniu można by zarzucić ?
Może to, że jednak jakoś ingeruje w dokonane wpisy (dzielenie) i zupełnie nie uwzględnia innych formatów, nie kończących się znakiem procentu. Ale właśnie uwaga Jacka zwróciła moją uwagę, na coś co bez przerwy ignorowałem:
Podczas wpisywania wartości 23.4 wystarczy dołożyć ten nieszczęsny znak procentu, a Access weźmie dzielenie na siebie !

Stąd już prosta droga do prób zautomatyzowania tego dodatkowego wpisu, podobnie jak robione jest to w MS Excel, po ustawieniu w kolumnie formatu procentowego.

Z pomocą przyszła mi użyta już wcześniej w rozwiązaniu z formułami a'la Excel, funkcja SetWindowText() (user32.dll)
Poniżej przedstawiam szkicowe rozwiązanie w module formularza, dla pola tekstowego o nazwie PoleProcent, zaprezentowane pierwotnie na grupie:

'***************************** code ***********************************
Private Declare Function GetFocus Lib "user32" () As Long
Private Declare Function SetWindowText Lib "user32" _
                  Alias "SetWindowTextA" _
                  ( _
                    ByVal Hwnd As Long, _
                    ByVal lpString As String _
                  ) As Long

Private Sub PoleProcent_Change()
  Dim poz As Integer
  Dim strWpis As String
  Dim Hwnd As Long
  Dim lngResult As Long

  On Error GoTo err_exit
  ' tylko kontrolka mająca focus jest oknem, które posiada uchwyt !
  Hwnd = GetFocus()
  poz = Me!PoleProcent.SelStart
  strWpis = Me!PoleProcent.Text
  
  ' czy zapis kończy się znakiem procentu ?
  If Len(strWpis) > 0 And Right(strWpis, 1) <> "%" Then
    strWpis = CStr(strWpis) & "%"
    ' jeśli nie, to go brutalnie tam wstawmy !
    lngResult = SetWindowText(Hwnd, strWpis)
    Me!PoleProcent.SelStart = poz
  End If
  
err_exit:
End Sub
'**************************end code ***********************************

Ale czy musimy dla każdego pola z osobna pisać jego procedurę zdarzenia OnChange ?
Oczywiście Nie!
Od tego mamy różne sposoby, m.in. własne klasy.

Rozwiązanie taką klasę wykorzystujące zawarte jest w pliku procent.zip
Podałem tam trzy sposoby na jej wykorzystanie, starając się coraz bardziej uprościć konieczne czynności w formularzu.
Polecam Waszej uwadze, bo w naszym bazodanowym dziale jest stosunkowo niewiele okazji, aby z klasami się "zaprzyjaźnić" ;-)

Uwagi:

Wady rozwiązania, cóż ...
Jest jedna poważna, wynikająca z mechanizmów samego Access'a. Dotyczy to tak samo prezentowanego przez Jacka Kubka rozwiązania z dzieleniem wpisu przez 100.
Otóż wszystko pięknie jak długo wpisujemy nasze procenty do pól pustych, ale co będzie jeśli do wypełnionego pola cofniemy się klikając weń myszką, albo wchodząc kursorem i naciskając F2 ?

Otóż Access uprawia tu swoje hazardy !
Chyba najczęściej (tak wyszło w trakcie moich, pewnie tendencyjnych ;-), testów) liczba 0.03457 sformatowana i widziana jako 3,46% po prostu ujawni swoją pełną postać, bez zaokrągleń: 3,457%
Jednak w pewnych wypadkach (trudnych jednoznacznie do określenia "kiedy") format procentowy znika, na rzecz formatu ogólnego: 0.03457

Czy to źle ?
Jak długo nie wtrącamy się w mechanizmy Access'a, nie! Raz tak, raz śmak, po wyjściu z pola i tak zobaczymy format procentowy ...
Gorzej, jeśli w tym drugim przypadku dokonamy jakiejś korekty wpisu, przykładowo:
ops, końcowa cyfra nie 7, ale 8 !
Zmieniamy zapis 0.03457 na 0.03458 i w tym momencie wyzwalana jest nasza procedura OnChange, która nie znajduje na końcu znaku procenta, dostawia go i otrzymujemy ... no właśnie: 0.03% !
buuuuuuuu !!!! Jak to teraz wytłumaczę klientowi ?

Czy znajdziemy na to jakieś obejście ? Nasuwa się jedno:
przy wejściu do pola sprawdzamy czy nie nastąpiła zmiana formatu z procentowego na ogólny.
Jeśli tak, to ustawiamy jakąś zmienną na True, która to wartość zablokuje wykonanie mojej procedury Change ...
Cóż, jestem w trakcie opracowywania takiego w miarę niezawodnego algorytmu ...

Inna wada:
Algorytm nie działa na związanych polach walutowym i decimal !
Wynika to z tego, że Access ignoruje wpis % w tych polach.

(2003/02/25) A oto obiecywane rozwiązanie: procent2.zip
W tym rozwiązaniu kontroluję zdarzenia OnKeyUp i OnMouseUp.
Procenty po wejściu do pola, nawet tego, który swą zawartość próbuje wyświetlić w formacie ogólnym, są odpowiednio korygowane.
Stwarza to jednak dodatkowe problemy ! Pole w takich wypadkach jest edytowane (a z nim cały rekord)
Jeśli ustawimy źródło wiersza na recordset nieedytowalny (np: Select Distinct ...), to uzyskamy nieciekawy efekt kasowania zawartości kontrolki.

Czy, podsumowując, słusznym będzie wniosek, że formatu procentowego najlepiej nie używać wcale ?
To juz zostawiam decyzji każdego z osobna.

Aby jednak zachęcić Kolegów do przyjrzenia się temu rozwiązaniu, dołączyłem dodatkową małą klasę, która odpowiada za poruszanie się po formularzu ciągłym w taki sam sposób jak w arkuszu (strzałki Up i Down)
Klasa rozpoznaje czy przypadkiem nie "wpadliśmy" do rozwiniętego pola kombi, gdzie strzałki powinny zachowywać się neutralnie, czyli chodzić po polu kombi, nie po rekordach.
Używać można to "jak małpka" ;-)
Wystarczy do docelowej bazy zaimportować klasę clsKeysUpDown oraz moduł basComboState (zapożyczony ze strony Ashisha)
oraz w kodzie formularza ciągłego zarejestrować instancję klasy:

'***************************** code ***********************************
Dim KeysNavi As New clsKeysUpDown 'zmienna na poziomie modułu
Private Sub Form_Open(Cancel As Integer)
  (...)
  'procedura rejestrująca w instancji klasy instancję naszego formularza
  KeysNavi.RegisterFormForKeyDownUp Me
End Sub
'************************* end code ***********************************

Krzysztof Naworyta (Kraków 25/02/2003)