Streaming components/pl
│
Deutsch (de) │
English (en) │
français (fr) │
日本語 (ja) │
polski (pl) │
português (pt) │
Wprowadzenie
Zwykle, gdy chcesz przechowywać dane na dysku lub w strumieniu sieciowym, musisz napisać kod, który ładuje i zapisuje każdą właściwość. W tym samouczku opisano, jak pisać klasy, które można ładować i zapisywać w strumieniach przy użyciu RTTI (RunTime Type Information) bez potrzeby pisania dodatkowego kodu ładowania/zapisywania.
Źródła Lazarus zawierają przykład, który pokazuje, jak zapisać TGroupBox z dzieckiem TCheckBox w strumieniu i jak ponownie odczytać strumień, aby odtworzyć kopię obu składników. Zobacz: <lazaruspath>/examples/componentstreaming/.
Łącząc użycie odpowiednich kontrolek RTTI, możesz zminimalizować ilość kodu potrzebnego do połączenia GUI programu z odpowiednimi danymi na pamięci dyskowej/sieciowej.
TComponent / TPersistent
Klasa TPersistent jest zdefiniowana w module Classes i wykorzystuje przełącznik kompilatora {$M+}. Ten przełącznik informuje kompilator o utworzeniu informacji o typie w czasie wykonywania programu (RTTI). Oznacza to, że klasy w tym module i wszystkie klasy potomne otrzymują nową sekcję klasową published. Właściwości published są tak samo widoczne, jak właściwości public, ale dodatkowo ich struktura jest dostępna w czasie wykonywania. Oznacza to, że wszystkie właściwości published mogą być odczytywane i zapisywane w czasie wykonywania. Na przykład IDE używa RTTI do pracy z komponentami, o których inaczej nic by nie wiedział.
TComponent rozszerza TPersistent o możliwość posiadania komponentów potomnych. Jest to ważne w przypadku przesyłania strumieniowego, gdzie jednym składnikiem jest root component (zwany także lookup root), zawierający listę komponentów potomnych.
TReader / TWriter
TReader i TWriter to klasy robocze, które odczytują ze strumienia lub zapisują do strumienia dowolny składnik TComponent (patrz CreateLRSReader i CreateLRSWriter). Używają one Driver do odczytu/zapisu przy użyciu specjalnego formatu danych. W tej chwili istnieje czytnik (TLRSObjectReader) i moduł zapisujący (TLRSObjectWriter) dla formatu obiektu binarnego zdefiniowanego w jednostce LResources oraz moduł zapisujący (TXMLObjectWriter) dla TDOMDocument zdefiniowany w Laz_XMLStreaming. Moduł LResources zawiera również funkcje do konwersji zwykłych formatu danych binarnych na tekst i z powrotem (LRSObjectBinaryToText, LRSObjectTextToBinary). LCL preferuje UTF8 dla łańcuchów, podczas gdy Delphi preferuje Widestrings. Zapewnia także pewne funkcje konwersji, dzięki czemu można łatwo poradzić sobie z przesyłaniem strumieniowym danych nie tylko w Lazarus, ale także w formacie binarnym Delphi.
Kolekcje strumieniowe
Zobacz tutaj TCollection/pl#Przesyłanie strumieniowe
Jest to pełny przykład, jak utworzyć listę elementów za pomocą klas TCollectionItem i TCollection, i przesyłać strumieniowo za pomocą TComponent.
Zapisywanie własnego komponentu - Część 1
Własny komponent może być np. tak prosty, jak:
type
TMyComponent = class(TComponent)
private
FID: integer;
published
property ID: integer read FID write FID;
end;
Zapisywanie komponentu do strumienia
Moduł LResources zawiera funkcję taką jak:
procedure WriteComponentAsBinaryToStream(AStream: TStream; AComponent: TComponent);
Która zapisuje komponent w formacie binarnym do strumienia. Na przykład:
procedure TForm1.Button1Click(Sender: TObject);
var
AStream: TMemoryStream;
begin
AStream:=TMemoryStream.Create;
try
WriteComponentAsBinaryToStream(AStream, AGroupBox);
... save stream somewhere ...
finally
AStream.Free;
end;
end;
Odczytywanie komponentu ze strumienia
Moduł LResources zawiera funkcję taką jak:
procedure ReadComponentFromBinaryStream(AStream: TStream;
var RootComponent: TComponent; OnFindComponentClass: TFindComponentClassEvent; TheOwner: TComponent = nil);
- AStream to strumień zawierający jeden komponent w formacie binarnym. Wszystko za tym komponentem w strumieniu nie jest odczytywane, w tym inne komponenty.
- RootComponent zawiera istniejący komponent, którego dane zostaną nadpisane, lub ma wartość nil i zostanie utworzony nowy komponent.
- OnFindComponentClass to funkcja używana przez TReader do pobierania klasy z nazw klas w strumieniu. Na przykład:
procedure TCompStreamDemoForm.OnFindClass(Reader: TReader;
const AClassName: string; var ComponentClass: TComponentClass);
begin
if CompareText(AClassName, 'TGroupBox') = 0 then
ComponentClass := TGroupBox
else if CompareText(AClassName, 'TCheckBox') = 0 then
ComponentClass := TCheckBox;
end;
- TheOwner jest właścicielem komponentu podczas tworzenia nowego komponentu.
Właściwości strumieniowalne
TReader i TWriter mają kilka ograniczeń co do typów, które mogą przesyłać strumieniowo:
- Wszystkie podstawowe typy Pascala mogą być przesyłane strumieniowo: string, integer, char, single, double, extended, byte, word, cardinal, shortint, method pointers, itp.
- Każda klasa TPersistent i dowolny potomek TPersistent mogą być przesyłane strumieniowo
- Rekordy, obiekty i klasy nie pochodzące od TPersistent nie mogą być przesyłane strumieniowo bez rozszerzenia istniejących metod TReader/TWriter. Aby przesyłać strumieniowo rekordy lub klasy i obiekty nie-TPersistent, musisz przesłonić niektóre metody TReader/TWriter. Zobacz poniżej #Streaming custom Data - DefineProperties.
Strumieniowanie danych niestandardowych - DefineProperties
Możesz przesyłać strumieniowo dodatkowe dowolne dane, zastępując metodę DefineProperties. Umożliwia to przesyłanie strumieniowe danych, które nie są podstawowym typem Pascala, oraz klas, które nie są potomkami TPersistent. Na przykład, aby przesłać strumieniowo zmienną rekordu FMyRect: TRect, która jest polem w twoim komponencie, dodaj do komponentu następujące trzy metody:
procedure DefineProperties(Filer: TFiler); override;
procedure ReadMyRect(Reader: TReader);
procedure WriteMyRect(Writer: TWriter);
Z następującym kodem:
procedure TMyComponent.DefineProperties(Filer: TFiler);
var
MyRectMustBeSaved: Boolean;
begin
inherited DefineProperties(Filer);
MyRectMustBeSaved := (MyRect.Left <> 0)
or (MyRect.Top <> 0)
or (MyRect.Right <> 0)
or (MyRect.Bottom <> 0);
Filer.DefineProperty('MyRect', @ReadMyRect, @WriteMyRect, MyRectMustBeSaved);
end;
procedure TMyComponent.ReadMyRect(Reader: TReader);
begin
with Reader do begin
ReadListBegin;
FMyRect.Left := ReadInteger;
FMyRect.Top := ReadInteger;
FMyRect.Right := ReadInteger;
FMyRect.Bottom := ReadInteger;
ReadListEnd;
end;
end;
procedure TMyComponent.WriteMyRect(Writer: TWriter);
begin
with Writer do begin
WriteListBegin;
WriteInteger(FMyRect.Left);
WriteInteger(FMyRect.Top);
WriteInteger(FMyRect.Right);
WriteInteger(FMyRect.Bottom);
WriteListEnd;
end;
end;
Spowoduje to zapisanie MyRect jako właściwości 'MyRect'.
Jeśli przesyłasz strumieniowo wiele pól TRect, prawdopodobnie nie chcesz powtarzać tego kodu za każdym razem. Moduł LResources zawiera przykład, w jaki sposób można napisać procedurę definiującą właściwość rect:
procedure DefineRectProperty(Filer: TFiler; const Name: string; ARect, DefaultRect: PRect);
Po napisaniu procedury definiowania właściwości rect powyższy kod można sprowadzić do:
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
DefineRectProperty(Filer, 'MyRect', @FMyRect, nil);
end;
Zapisywanie własnego komponentu - Część 2
Teraz przykład można rozszerzyć i możemy użyć dowolnych właściwości za pomocą tylko kilku wierszy kodu:
type
TMyComponent = class(TComponent)
private
FID: integer;
FRect1: TRect;
FRect2: TRect;
protected
procedure DefineProperties(Filer: TFiler); override;
public
property Rect1: TRect read FRect1 write FRect1;
property Rect2: TRect read FRect2 write FRect2;
published
property ID: integer read FID write FID;
end;
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited DefineProperties(Filer);
DefineRectProperty(Filer, 'Rect1', @FRect1,nil);
DefineRectProperty(Filer, 'Rect2', @FRect2,nil);
end;
Ten komponent może teraz zostać zapisany, załadowany lub użyty przez kontrolki RTTI. Nie musisz pisać żadnego kodu.
Zapisywanie i odczytywanie komponentów z/do LFM
Zobacz przykładowe funkcje z modułu lresources: ReadComponentFromTextStream i WriteComponentAsTextToStream.
Zapisywanie i odczytywanie komponentów z/do XML
Przesyłanie strumieniem komponentów jest proste: Zobacz przykład w lazarus/examples/xmlstreaming/.
Nazwy
- Wszystkie komponenty należące do jednego komponentu (właściciela) muszą mieć odrębne nazwy. Tak więc dwa formularze będące własnością aplikacji muszą mieć odrębne nazwy. Dwie etykiety na formularzu muszą mieć różne nazwy. Ale dwie etykiety na dwóch różnych formularzach mogą mieć tę samą nazwę. Formularz może mieć takie samo imię jak jedno z jego dzieci.
- TComponent.Name może być pusta i można mieć więcej niż jeden komponent bez nazwy. TWriter zapisze to, ale TReader nie znajdzie takiego komponentu i odczyt się nie powiedzie. Dlatego Inspektor obiektów IDE nie pozwala na to.
- Nazwy muszą być poprawnymi identyfikatorami Pascala IsValidIdent.
- Podczas odwoływania się do innych formularzy: Wszystkie komponenty główne (formularze, moduły danych, ...), do których odnoszą się inne (formularze itp.), muszą mieć unikalne nazwy. Nie muszą być własnością aplikacji, ale wtedy programista musi upewnić się, że nazwy są unikalne. Formularze i moduły danych znaleźć można za pośrednictwem obiektu Screen, w którym wszystkie formularze i moduły danych rejestrują się automatycznie.
- Możesz utworzyć wiele formularzy o nazwie Form1, na przykład poprzez TForm1.Create(nil). Jeśli Form2 odwołuje się do Form1.OpenDialog, wówczas w obiekcie Screen jako pierwsza występuje Form1.
- Form1 i osadzona w niej ramka frame mogą mieć jednocześnie etykiety o nazwie Label1. Gdy pojawia się odniesienie do Label1, powinna być ona unikalne w całym formularzu, w tym we wszystkich osadzonych ramkach. Dlatego zaleca się nadanie wszystkim komponentom unikalnych nazw.
- Globalna poprawka: TReader odczytuje strumień komponentu. Jeśli znajdzie osadzoną ramkę (frame), tworzony jest drugi TReader, który odczytuje strumień ramek. Następnie powraca i kontynuuje. Odniesienia do innych komponentów (np. Form1.Button1) są zapisywane na globalnej liście poprawek w module classes (patrz GetFixupReferenceNames). Odniesienia są poprawiane po odczytaniu.
- TReader i TWriter używają specjalnej nazwy Owner do odniesienia się do aktualnego właściciela.
Wniosek
RTTI to potężny mechanizm umożliwiający łatwe przesyłanie strumieniowe całych klas. RTTI pomaga również uniknąć wielokrotnego pisania nudnego kodu ładowania/zapisywania.