furious programming
2018-02-18 12:14

Dziś co nieco na temat komponentów, a dokładniej, o tekstowych właściwościach i ich przechowywaniu.

Opublikowane właściwości komponentów mogą posiadać wartości domyślne – dzięki temu nie są one zapisywane w plikach .lfm. To definiuje się za pomocą słówka default na końcu deklaracji właściwości. Właściwości tekstowe (typu String) też mogą posiadać wartości domyślne, nie składowane w plikach .lfm, jednak składnia jest nieco inna.

Otóż użyć należy słówka stored, a po nim podać wartość logiczną lub metodę zwracającą dane tego typu. Przykład:

published
  property Foo: String read FFoo write SetFoo stored True;

Powyższe oznacza, że wartość zawsze będzie przechowywana w pliku zasobów. O wiele częściej spotykaną praktyką jest używanie metody w stylu Is*Stored, zamiast literałów:

private
  function IsFooStored(): Boolean;
published
  property Foo: String read FFoo write SetFoo stored IsFooStored;

Metoda IsFooStored może wyglądać np. tak:

function TMyControl.IsFooStored(): Boolean;
begin
  Result := FFoo <> 'foo';
end;

A co jeśli mamy kilka właściwości tekstowych? Pasuje je zgrupować, czyli zamiast tworzyć wiele osobnych pól, stworzyć jedną macierz i odwoływać się do konkretnych pól za pomocą indeksu. Preferuję enumy zamiast zwykłych liczb. Przykład:

const
  MY_CONTROL_FOO = 'foo';
  MY_CONTROL_BAR = 'bar';
  MY_CONTROL_BAZ = 'baz';
 
type
  TMyControlTextKind = (mctFoo, mctBar, mctBaz);
  TMyControlTextsArr = array [TMyControlTextKind] of String;
 
{..}
 
private
  FTexts: TMyControlTextsArr;
private
  function GetText(AKind: TMyControlTextKind): String;
  procedure SetText(AKind: TMyControlTextKind; const AText: String);
published
  property Foo: String index mctFoo read GetText write SetText;
  property Bar: String index mctBar read GetText write SetText;
  property Baz: String index mctBaz read GetText write SetText;

Miodzio. Teraz czas na wartości domyślne. W dokumentacji czytamy (w jakże zacnym punkcie):

6.6.6 Storage information
The stored specifier should be either a boolean constant, a boolean field of the class, or a parameterless function which returns a boolean result. […]

Czyli dla każdej właściwości trzeba zrobić osobną metodę Is*Stored… Odczyt i zapis dało się ładnie uprościć za pomocą indeksowania, a tu taka lipa… Na szczęście przywykłem do tego, aby nie wierzyć dokumentacji (tym bardziej, że najnowsza wersja kompilatora to 3.0.4, a dokumentacja jest dla 3.0.2), więc sprawdziłem, czy faktycznie metoda Is*Stored musi być bezparametrowa:

const
  MY_CONTROL_TEXTS: TMyControlTextsArr = (MY_CONTROL_FOO, MY_CONTROL_BAR, MY_CONTROL_BAZ);
 
{..}
 
private
  function IsTextStored(AKind: TMyControlTextKind): Boolean;
published
  property Foo: String index mctFoo read GetText write SetText stored IsTextStored;
  property Bar: String index mctBar read GetText write SetText stored IsTextStored;
  property Baz: String index mctBaz read GetText write SetText stored IsTextStored;
 
{..}
 
function TMyControl.IsTextStored(AKind: TMyControlTextKind): Boolean;
begin
  Result := FTexts[AKind] <> MY_CONTROL_TEXTS[AKind];
end;

No i działa. Nie tylko kompiluje się bezbłędnie, ale i działa właściwie – poprawnie waliduje ciągi i do pliku .lfm zapisuje je wyłącznie wtedy, gdy różnią się od wartości uznawanych za domyślne (czyli tych ze stałych). To miło, bo dzięki temu można mieć wiele właściwości łańcuchowych w jednej klasie, ale tylko jedną metodę określającą czy posiadają domyślne dane (i tak samo: jedną do odczytu konkretnego ciągu i jedną do zapisu).

Jak to dobrze być dociekliwym. :D

#free-pascal #lazarus

spartanPAGE

właściwości w free pascalu to chyba najbardziej rozwlekłe syntaktycznie właściwości jakie w ogóle są

furious programming

Nie wiem, nie orientuję się jak to dokładnie wygląda w innych językach, więc trudno określić. Nie dotyczy to wyłącznie Free Pascala – taka sama funkcjonalność wspierana jest przez Delphi (choć co do parametrowych metod Is*Stored nie mam pewności).

To co opisałem wyżej to wierzchołek góry lodowej możliwości właściwości – różnych opcji jest całkiem sporo. Nawet samo słówko default można wykorzystać do dwóch całkiem różnych zabiegów (określenie wartości domyślnej właściwości typu prostego, lub oznaczenie właściwości indeksowanej jako domyślnej, dzięki czemu nie jest wymagane pisanie jej nazwy).

Gdybym miał opisać wszystko co dotyczy właściwości, to materiału wystarczyłoby na kolejne dwa przydługie wpisy. :]

cerrato

" materiału wystarczyłoby na kolejne dwa przydługie wpisy." - dawaj ziomek :D
Chętnie poczytamy.

furious programming

@cerrato: chyba nawet na więcej niż dwa wpisy. Oprócz samych różnych zapisów deklaracji właściwości (których jest z kilkanaście), trzeba by jeszcze wspomnieć o opcjach rozszerzających edytor formularzy, a które również właściwości dotyczą. Z jednej strony było by to nic innego jak przepisanie dokumentacji, ale z drugiej strony, zawsze można by napisać o swoich patentach.

Jednym z moich ulubionych rzeczy związanych z właściwościami komponentów jest ich grupowanie w gałęzie. Dzięki temu, dla przykładu, zawartość okna Object Inspector domyślnie mieści się w całości na przysłowiowym ekranie, ale jak porozwija się gałązki to wychodzą trzy ekrany. To pozwala na wygodniejszą pracę w edytorze formularzy i przyjemniejszą składnię, bo nazwy właściwości wychodzą krótkie.

Może następny wpis na temat Lazarusa będzie właśnie o tym. ;)

cerrato

Tak w ogóle - korzystając z okazji - chciałbym na Twoje ręce (jako moderatora Delphi) złożyć wniosek o dopisanie Lazarusa do nazwy działu. Byłoby to w pewien sposób jego uhonorowanie / wskazanie, że jest czymś wartym uwagi.

furious programming

Nazwa kategorii dotyczy języków programowania, nie narzędzi. Obecna informuje, że zawiera wątki dotyczące Delphi i wszystkich innych języków wywodzących się z klasycznego Pascala. Tak samo sprawa wygląda np. z kategorią C/C++.

Natomiast całkiem niedawno uzupełniałem opisy kategorii (widoczne na stronie z ich listą), dodając informację o Free Pascalu, Lazarusie, Code Typhoon i Oxygene. Tego brakowało już od dawna, więc trzeba było co nieco poprawić.

cerrato

To jeszcze można byłoby zrobić małą dyskusję, jak traktować Delphi. Moim zdaniem (aczkolwiek są różne opinie/podejścia, więc nie musicie się zgadzać) w przypadku Delphi językiem jest Object Pascal, a słowo Delphi oznacza właśnie tą całą "otoczkę" - głównie IDE i VCL, czyli to co nazwałeś "narzędziem".