Streaming components/pl: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Streaming components/pl
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Wprowadzenie | |
| 2 | + | ||
| 3 | + | 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. | |
| 4 | + | ||
| 5 | + | Ź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/`. | |
| 6 | + | ||
| 7 | + | Łącząc użycie odpowiednich [[RTTI controls|kontrolek RTTI]], możesz zminimalizować ilość kodu potrzebnego do połączenia GUI programu z odpowiednimi danymi na pamięci dyskowej/sieciowej. | |
| 8 | + | ||
| 9 | + | ## TComponent / TPersistent | |
| 10 | + | ||
| 11 | + | 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ł. | |
| 12 | + | ||
| 13 | + | **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. | |
| 14 | + | ||
| 15 | + | ## TReader / TWriter | |
| 16 | + | ||
| 17 | + | 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. | |
| 18 | + | ||
| 19 | + | ## Kolekcje strumieniowe | |
| 20 | + | ||
| 21 | + | Zobacz tutaj [[TCollection/pl|TCollection/pl#Przesyłanie strumieniowe]] | |
| 22 | + | ||
| 23 | + | Jest to pełny przykład, jak utworzyć listę elementów za pomocą klas **TCollectionItem** i **TCollection**, i przesyłać strumieniowo za pomocą **TComponent**. | |
| 24 | + | ||
| 25 | + | ## Zapisywanie własnego komponentu - Część 1 | |
| 26 | + | ||
| 27 | + | Własny komponent może być np. tak prosty, jak: | |
| 28 | + | ||
| 29 | + | ``` pascal | |
| 30 | + | type | |
| 31 | + | TMyComponent = class(TComponent) | |
| 32 | + | private | |
| 33 | + | FID: integer; | |
| 34 | + | published | |
| 35 | + | property ID: integer read FID write FID; | |
| 36 | + | end; | |
| 37 | + | ``` | |
| 38 | + | ||
| 39 | + | ## Zapisywanie komponentu do strumienia | |
| 40 | + | ||
| 41 | + | Moduł [[doc:lcl/lresources|LResources]] zawiera funkcję taką jak: | |
| 42 | + | ||
| 43 | + | ``` pascal | |
| 44 | + | procedure WriteComponentAsBinaryToStream(AStream: TStream; AComponent: TComponent); | |
| 45 | + | ``` | |
| 46 | + | ||
| 47 | + | Która zapisuje komponent w formacie binarnym do strumienia. Na przykład: | |
| 48 | + | ||
| 49 | + | ``` pascal | |
| 50 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 51 | + | var | |
| 52 | + | AStream: TMemoryStream; | |
| 53 | + | begin | |
| 54 | + | AStream:=TMemoryStream.Create; | |
| 55 | + | try | |
| 56 | + | WriteComponentAsBinaryToStream(AStream, AGroupBox); | |
| 57 | + | ... save stream somewhere ... | |
| 58 | + | finally | |
| 59 | + | AStream.Free; | |
| 60 | + | end; | |
| 61 | + | end; | |
| 62 | + | ``` | |
| 63 | + | ||
| 64 | + | ## Odczytywanie komponentu ze strumienia | |
| 65 | + | ||
| 66 | + | Moduł LResources zawiera funkcję taką jak: | |
| 67 | + | ||
| 68 | + | ``` pascal | |
| 69 | + | procedure ReadComponentFromBinaryStream(AStream: TStream; | |
| 70 | + | var RootComponent: TComponent; OnFindComponentClass: TFindComponentClassEvent; TheOwner: TComponent = nil); | |
| 71 | + | ``` | |
| 72 | + | ||
| 73 | + | - AStream to strumień zawierający **jeden** komponent w formacie binarnym. Wszystko za tym komponentem w strumieniu nie jest odczytywane, w tym inne komponenty. | |
| 74 | + | - RootComponent zawiera istniejący komponent, którego dane zostaną nadpisane, lub ma wartość nil i zostanie utworzony nowy komponent. | |
| 75 | + | - OnFindComponentClass to funkcja używana przez TReader do pobierania klasy z nazw klas w strumieniu. Na przykład: | |
| 76 | + | ||
| 77 | + | ``` pascal | |
| 78 | + | procedure TCompStreamDemoForm.OnFindClass(Reader: TReader; | |
| 79 | + | const AClassName: string; var ComponentClass: TComponentClass); | |
| 80 | + | begin | |
| 81 | + | if CompareText(AClassName, 'TGroupBox') = 0 then | |
| 82 | + | ComponentClass := TGroupBox | |
| 83 | + | else if CompareText(AClassName, 'TCheckBox') = 0 then | |
| 84 | + | ComponentClass := TCheckBox; | |
| 85 | + | end; | |
| 86 | + | ``` | |
| 87 | + | ||
| 88 | + | - TheOwner jest właścicielem komponentu podczas tworzenia nowego komponentu. | |
| 89 | + | ||
| 90 | + | ## Właściwości strumieniowalne | |
| 91 | + | ||
| 92 | + | TReader i TWriter mają kilka ograniczeń co do typów, które mogą przesyłać strumieniowo: | |
| 93 | + | ||
| 94 | + | - Wszystkie podstawowe typy Pascala mogą być przesyłane strumieniowo: string, integer, char, single, double, extended, byte, word, cardinal, shortint, method pointers, itp. | |
| 95 | + | ||
| 96 | + | - Każda klasa TPersistent i dowolny potomek TPersistent mogą być przesyłane strumieniowo | |
| 97 | + | ||
| 98 | + | - 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. | |
| 99 | + | ||
| 100 | + | ## Strumieniowanie danych niestandardowych - DefineProperties | |
| 101 | + | ||
| 102 | + | 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: | |
| 103 | + | ||
| 104 | + | ``` pascal | |
| 105 | + | procedure DefineProperties(Filer: TFiler); override; | |
| 106 | + | procedure ReadMyRect(Reader: TReader); | |
| 107 | + | procedure WriteMyRect(Writer: TWriter); | |
| 108 | + | ``` | |
| 109 | + | ||
| 110 | + | Z następującym kodem: | |
| 111 | + | ||
| 112 | + | ``` pascal | |
| 113 | + | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 114 | + | var | |
| 115 | + | MyRectMustBeSaved: Boolean; | |
| 116 | + | begin | |
| 117 | + | inherited DefineProperties(Filer); | |
| 118 | + | MyRectMustBeSaved := (MyRect.Left <> 0) | |
| 119 | + | or (MyRect.Top <> 0) | |
| 120 | + | or (MyRect.Right <> 0) | |
| 121 | + | or (MyRect.Bottom <> 0); | |
| 122 | + | Filer.DefineProperty('MyRect', @ReadMyRect, @WriteMyRect, MyRectMustBeSaved); | |
| 123 | + | end; | |
| 124 | + | ||
| 125 | + | procedure TMyComponent.ReadMyRect(Reader: TReader); | |
| 126 | + | begin | |
| 127 | + | with Reader do begin | |
| 128 | + | ReadListBegin; | |
| 129 | + | FMyRect.Left := ReadInteger; | |
| 130 | + | FMyRect.Top := ReadInteger; | |
| 131 | + | FMyRect.Right := ReadInteger; | |
| 132 | + | FMyRect.Bottom := ReadInteger; | |
| 133 | + | ReadListEnd; | |
| 134 | + | end; | |
| 135 | + | end; | |
| 136 | + | ||
| 137 | + | procedure TMyComponent.WriteMyRect(Writer: TWriter); | |
| 138 | + | begin | |
| 139 | + | with Writer do begin | |
| 140 | + | WriteListBegin; | |
| 141 | + | WriteInteger(FMyRect.Left); | |
| 142 | + | WriteInteger(FMyRect.Top); | |
| 143 | + | WriteInteger(FMyRect.Right); | |
| 144 | + | WriteInteger(FMyRect.Bottom); | |
| 145 | + | WriteListEnd; | |
| 146 | + | end; | |
| 147 | + | end; | |
| 148 | + | ``` | |
| 149 | + | ||
| 150 | + | Spowoduje to zapisanie MyRect jako właściwości 'MyRect'. | |
| 151 | + | ||
| 152 | + | 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: | |
| 153 | + | ||
| 154 | + | ``` pascal | |
| 155 | + | procedure DefineRectProperty(Filer: TFiler; const Name: string; ARect, DefaultRect: PRect); | |
| 156 | + | ``` | |
| 157 | + | ||
| 158 | + | Po napisaniu procedury definiowania właściwości rect powyższy kod można sprowadzić do: | |
| 159 | + | ||
| 160 | + | ``` pascal | |
| 161 | + | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 162 | + | begin | |
| 163 | + | inherited DefineProperties(Filer); | |
| 164 | + | DefineRectProperty(Filer, 'MyRect', @FMyRect, nil); | |
| 165 | + | end; | |
| 166 | + | ``` | |
| 167 | + | ||
| 168 | + | ## Zapisywanie własnego komponentu - Część 2 | |
| 169 | + | ||
| 170 | + | Teraz przykład można rozszerzyć i możemy użyć dowolnych właściwości za pomocą tylko kilku wierszy kodu: | |
| 171 | + | ||
| 172 | + | ``` pascal | |
| 173 | + | type | |
| 174 | + | TMyComponent = class(TComponent) | |
| 175 | + | private | |
| 176 | + | FID: integer; | |
| 177 | + | FRect1: TRect; | |
| 178 | + | FRect2: TRect; | |
| 179 | + | protected | |
| 180 | + | procedure DefineProperties(Filer: TFiler); override; | |
| 181 | + | public | |
| 182 | + | property Rect1: TRect read FRect1 write FRect1; | |
| 183 | + | property Rect2: TRect read FRect2 write FRect2; | |
| 184 | + | published | |
| 185 | + | property ID: integer read FID write FID; | |
| 186 | + | end; | |
| 187 | + | ||
| 188 | + | procedure TMyComponent.DefineProperties(Filer: TFiler); | |
| 189 | + | begin | |
| 190 | + | inherited DefineProperties(Filer); | |
| 191 | + | DefineRectProperty(Filer, 'Rect1', @FRect1,nil); | |
| 192 | + | DefineRectProperty(Filer, 'Rect2', @FRect2,nil); | |
| 193 | + | end; | |
| 194 | + | ``` | |
| 195 | + | ||
| 196 | + | Ten komponent może teraz zostać zapisany, załadowany lub użyty przez [[RTTI controls|kontrolki RTTI]]. Nie musisz pisać żadnego kodu. | |
| 197 | + | ||
| 198 | + | ## Zapisywanie i odczytywanie komponentów z/do LFM | |
| 199 | + | ||
| 200 | + | Zobacz przykładowe funkcje z modułu lresources: ReadComponentFromTextStream i WriteComponentAsTextToStream. | |
| 201 | + | ||
| 202 | + | ## Zapisywanie i odczytywanie komponentów z/do XML | |
| 203 | + | ||
| 204 | + | Przesyłanie strumieniem komponentów jest proste: Zobacz przykład w lazarus/examples/xmlstreaming/. | |
| 205 | + | ||
| 206 | + | ## Nazwy | |
| 207 | + | ||
| 208 | + | - 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. | |
| 209 | + | ||
| 210 | + | - 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. | |
| 211 | + | ||
| 212 | + | - Nazwy muszą być poprawnymi identyfikatorami Pascala [IsValidIdent](http://lazarus-ccr.sourceforge.net/docs/rtl/sysutils/isvalidident.html). | |
| 213 | + | ||
| 214 | + | - 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. | |
| 215 | + | ||
| 216 | + | - 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*. | |
| 217 | + | ||
| 218 | + | - *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. | |
| 219 | + | ||
| 220 | + | - 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](http://lazarus-ccr.sourceforge.net/docs/rtl/classes/getfixupreferencenames.html)). Odniesienia są poprawiane po odczytaniu. | |
| 221 | + | ||
| 222 | + | - TReader i TWriter używają specjalnej nazwy *Owner* do odniesienia się do aktualnego właściciela. | |
| 223 | + | ||
| 224 | + | ## Wniosek | |
| 225 | + | ||
| 226 | + | 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. | |
| 227 | + | ||
| 228 | + | ## Zobacz także | |
| 229 | + | ||
| 230 | + | - [[RTTI controls|Kontrolki RTTI]] | |
| 231 | + | - [[OpenGL Tutorial|Samouczek OpenGL]] | |
| 232 | + | ||
| 233 | + | --- | |
| 234 | + | *From the Free Pascal wiki, page "Streaming components/pl", as it stood on 2023-01-31 after a change by Slawek, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
