Autor Téma: RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace  (Přečteno 1337 krát)

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Vážená komunito,

chtěl bych si udělat DFM (FMX) parser tak, aby po spuštění aplikace došlo k jeho načtení, rozparsování a použití. Díky tomu bych si mohl dodatečně upravovat property všech komponent v aplikaci aniž bych sahal do zdrojového kódu. Využívám k tomu RTTI (System.Rtti), které je pro mě nové. Kód funguje dobře, jen mi občas property "neprojde" přes kontrolu pomocí funkce IsPublishedProp. Pokud tato funkce vrátí false, tak z nějakého důvodu není property viditelná a nelze ji upravit. Je možné, že tuto funkci používám špatně. Mám ale vlastní komponentu u které na 100% vím, že property je v published sekci třídy a stejně neprojde...

V kódu níže je jednoduché načtení DFM (FMX) souboru do stringlistu a spuštění parsování řádků. To parsování se volá pomocí Parse_Line(Application); kdy dochází k rekurzivnímu volání podle hiararchie objektu.

Kód: Delphi [Vybrat]
  1. procedure Load_DFM_FMX(const AFileName: string);
  2. var
  3.   line_number: integer;
  4.   sl: TStringList;
  5.   rttiContext: TRttiContext;
  6. begin
  7.   if FileExists(AFileName) then
  8.   begin  
  9.     sl := TStringList.Create;
  10.     try
  11.       sl.LoadFromFile(AFileName);
  12.  
  13.       rttiContext := TRttiContext.Create;
  14.       try
  15.         line_number := 0;
  16.         while line_number <= sl.Count - 1 do Parse_Line(Application);
  17.  
  18.       finally
  19.         rttiContext.Free;
  20.       end;
  21.  
  22.     finally
  23.       sl.Free;
  24.     end;
  25.   end;
  26. end;
  27.  

a zde je procedura na samotné parsování řádků:

Kód: Delphi [Vybrat]
  1. procedure Parse_Line(const Owner: TComponent);
  2. var
  3.   s: string;
  4.   Component: TComponent;
  5.   rttiType: TRttiType;
  6.   rttiProperty: TRttiProperty;
  7.   sValue: string;
  8.   sProperty: string;
  9.   Obj: TObject;
  10. begin
  11.   try
  12.     s := trim(sl[line_number]);
  13.     if s <> '' then
  14.       if (pos('object ', s) = 1) and (pos(': ', s) > 1) then
  15.       begin
  16.         Component := Owner.FindComponent(copy(s, 8, pos(': ', s) - 8));
  17.         if Component <> nil then
  18.         begin
  19.           rttiType := rttiContext.GetType(Component.ClassType);
  20.           if rttiType <>  nil then
  21.           begin
  22.             Obj := Component;
  23.  
  24.             inc(line_number);
  25.             while line_number <= sl.Count - 1 do
  26.             begin
  27.               try
  28.                 s := trim(sl[line_number]);
  29.                 if s <> '' then
  30.                   if (pos('object ', s) = 1) and (pos(': ', s) > 1) then Parse_Line(Component)
  31.                     else if s = 'end' then break
  32.                     else if (pos(' = ', s) > 1) then
  33.                     begin
  34.                       sProperty := copy(s, 1, pos(' = ', s) - 1);
  35.                       sValue    := copy(s, pos(' = ', s) + 3, length(s));
  36.  
  37.                       rttiProperty := nil;
  38.                       while length(sProperty) > 0 do
  39.                         if pos('.', sProperty) > 1 then
  40.                         begin
  41.                           s := copy(sProperty, 1, pos('.', sProperty) - 1);
  42.                           delete(sProperty, 1, length(s) + 1);
  43.  
  44.                           if rttiProperty = nil then rttiProperty := rttiType.GetProperty(s)
  45.                                                 else rttiProperty := rttiProperty.PropertyType.GetProperty(s);
  46.  
  47.                           Obj := rttiProperty.GetValue(Component).AsObject;
  48.                         end
  49.                         else
  50.                         begin
  51.                           s         := sProperty;
  52.                           sProperty := '';
  53.  
  54.                           if rttiProperty = nil then rttiProperty := rttiType.GetProperty(s)
  55.                                                 else rttiProperty := rttiProperty.PropertyType.GetProperty(s);
  56.                         end;
  57.  
  58.                       if (Obj <> nil) and (rttiProperty <> nil) and (length(sValue) > 0) then
  59.                       begin
  60.                         if not IsPublishedProp(Obj, rttiProperty.Name) then Error
  61.                         else
  62.                           case rttiProperty.PropertyType.TypeKind of
  63.                             tkInteger: SetOrdProp(Obj, rttiProperty.Name, TryStrToIntDef(sValue, GetOrdProp(Obj, rttiProperty.Name), true));
  64.                             tkInt64: SetInt64Prop(Obj, rttiProperty.Name, TryStrToInt64Def(sValue, GetInt64Prop(Obj, rttiProperty.Name), true));
  65.                             tkFloat: SetFloatProp(Obj, rttiProperty.Name, TryStrToFloatDef(sValue, GetFloatProp(Obj, rttiProperty.Name), true));
  66.                             tkEnumeration: SetEnumProp(Obj, rttiProperty.Name, sValue);
  67.                             tkString,
  68.                             tkLString,
  69.                             tkWString,
  70.                             tkUString,
  71.                             tkChar,
  72.                             tkWChar: SetStrProp(Obj, rttiProperty.Name, copy(sValue, 2, length(sValue) - 2));
  73.                             tkSet: SetSetProp(Obj, rttiProperty.Name, sValue);
  74.                             else Error;
  75.                           end;
  76.                       end
  77.                       else Error;
  78.                     end;
  79.  
  80.                 except
  81.                   on E: Exception do
  82.                     Error(E);
  83.                 end;
  84.  
  85.                 inc(line_number);
  86.               end;
  87.             end;
  88.           end;
  89.         end;
  90.  
  91.   except
  92.     on E: Exception do
  93.       Error(E);
  94.   end;
  95.  
  96.   inc(line_number);
  97. end;
  98.  

Na zkladní property jako Caption, Position to funguje dobře. Občas ale narazím na property, která mi vypadne na řádku č.60, tj neprojde přes podmínku s funkcí IsPublishedProp.

Rád bych si ještě rozšířil část s case blokem o další typy jako metody, pole aj. K tomu ale potřebuji přijít na to, proč IsPublishedProp vrací false i u property, které jsou published a standardně viditelné/dostupné.

Pokud by si to někdo chtěl vyzkoušet, tak lze na formulář umístit TButton a poté si z DFM (FMX) vytáhnout tento objekt a změnit Text property. Tato property též neprojde přes podmínku IsPublishedProp ačkoliv by asi měla.

Děkuji za případné rady.
 

« Poslední změna: 30-04-2020, 13:46:05 od age.new »

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 3002
  • Karma: 108
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #1 kdy: 30-04-2020, 14:59:47 »
Chapu to, ze se snazis duplikovat streamovaci system komponent z RTL? Proc ho nepouzijes rovnou? Myslim ze by melo fungovat neco jako

Kód: Delphi [Vybrat]
  1.   Form := TStandardForm.CreateNew(Application);
  2.   ReadComponentRes('TStandardForm', Form); // asi existuje neco cemu predhodis stream
  3.  
  4.   Form.Show;
  5.  
  6. tady pres nejakou komponentu designera, je jich nekolik zmenis co chces, a pak to podobne ulozis.
  7.  

Teda nikdy jsem to nedelal, ale tak nejak to funguje
Embarcadero MVP - Czech republic

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #2 kdy: 30-04-2020, 15:26:33 »
To musím vyzkoušet. Kdyby bylo něco, co to udělá za mě, tak by to bylo super.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #3 kdy: 04-05-2020, 09:01:36 »
Tak jsem se blíže podíval na ReadComponentRes. Tato funkce bohužel nevyhovuje, jelikož objekty znovu vytváří a pokud již existují, tak to padne do chyby. Nenašel jsem jiné řešení ani na internetu. Proto to obchází přes RTTI, tak jak jsem psal.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3335
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #4 kdy: 04-05-2020, 09:57:12 »
Tak jsem se blíže podíval na ReadComponentRes. Tato funkce bohužel nevyhovuje, jelikož objekty znovu vytváří a pokud již existují, tak to padne do chyby.
A byl by problem tu komponentu testne pred ctenim killnout a znovu ji tim ctenim vytvorit?

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #5 kdy: 04-05-2020, 10:29:03 »
Vymazání komponenty a opětovné vytvoření podle res souboru by vneslo více problémů, než jsem původně zamýšlel.

Původě jsem chtěl mít externí soubor pro úpravu "vizuální" části některých komponent. A pak bych si načetl takový vizuální styl, který bych potřeboval. Kdybych chtěl například změnit barvu TRectangle komponenty, pouze bych zapsal toto:

Kód: Delphi [Vybrat]
  1. object Ctverec: TRectangle
  2.   Fill.Color = xFFB1B1B1
  3. end
  4.  

Soubor DFM (FMX) mi vyhovoval, než jsem narazil na problém s IsPublishedProp, kdy u některých published property vrací false. Nepřišel jsem na to, jak to obejít.

Opravdu v Delphi není jiný způsob, jak převést string na property? Např:
TComponent(Ctverec).Property('Fill.Color') := ...

Děkuji.
« Poslední změna: 04-05-2020, 10:33:28 od age.new »

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3335
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #6 kdy: 04-05-2020, 16:07:45 »
Soubor DFM (FMX) mi vyhovoval, než jsem narazil na problém s IsPublishedProp, kdy u některých published property vrací false. Nepřišel jsem na to, jak to obejít.
A potrebujes to vubec testovat? Rozhodujici by melo byt, jestli GetProperty neco vrati, protoze jestli se nepletu, tak informace o property v RTTI jsou jen pro published.

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #7 kdy: 05-05-2020, 07:30:54 »
Ten test je jediný způsob, jak zamezit pádu do chyby v případě čtení / zápisu hodnoty do požadované property. Celý ten kód, jak jsem uvedl, projde až k case části a do té doby vše vypadá ok, i proměnná rttiProperty.

V RTTI jsou informace jen o published property. Jak je ale možné, že například property TButton.Text neprojde přes podmínku IsPublishedProp i když je deklarovaná v published sekci? Mám vlastní FMX komponenty u kterých jiné 100% published property též neprojdou.

 

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3335
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #8 kdy: 05-05-2020, 09:26:29 »
Excellent
Rated 1 time
Ten test je jediný způsob, jak zamezit pádu do chyby v případě čtení / zápisu hodnoty do požadované property
Jestlize sam pises, ze RTTI se generuje jen pro published properties,  tak pak je ten text nadbytecny, ne? Najit pres RTTI muzes jedine published property.

Citace
Jak je ale možné, že například property TButton.Text neprojde přes podmínku IsPublishedProp i když je deklarovaná v published sekci?
A co takhle chyba v tom tvem parseru?  ;)

Letmo jsem se na tvuj kod dival a dost se mi to nelibi:

- pozitivni logika a noreni bloku do sebe
- nevyclenene invarianty, takze clovek u rady takovych tech kopirovanych copy(...) nevi, s cim vlastne operuje, kdyz by to trasoval
- ale hlavne: matouci pojmenovani promennych a jejich pouzivani k vice ucelum. Jestli to ctu dobre, tak tam mas chybu prave u toho obj, protoze jakmile se ti vyskytne reference na property.property, tak sice nastavis obj na lokalni instanci referovaneho objektu, ale ztratis tak instanci puvodni komponenty, ktera pak uz napr. property Text skutecne nema. A pokud v .DFM nasleduje odkaz na property komponenty po odkazu na property.property, tak ji to nenajde, takze to spadne,

Moje doporuceni je poradne to prepsat a zejmena udelat poradek v promennych
s -> sLine, jinde s -> sElement
Component -> sCompName
obj -> pouzivat jen skutecne jako anonymni objekt na jednu operaci, jehoz property se hodnota prirazuje a pro component zavest napr. oCompInst a obj spravne reinicializovat -> ted to mas spatne pred cyklem etc... etc...

Taky bys ta jmena mel sjednotit: kdyz pouzivas Madarskou notaci sValue, tak bys ji mel pouzivat vsude nebo nikde (BTW, Madarska notace je prezitek z jazyku bez silne typove kontroly jako ASM a C), symbolicka jmena byvaji v Delphi v CamelCase bez podtrzitek aj. rozhodnout se, jestli budes odlisovat promenne 1. malym pismenem (jako v C-like svete) nebo ne (jako v Delphi tj. obj vs. Obj).

A kazdou promennou pouzivat jen k jedinemu ucelu. Prace je s tim minimalni, v run-time to nic nestoji, protoze je to automaticka promenna na zasobniku, ale srozumitelnost a tim padem i spolehlivost toho kodu se vyznamne zvysi, vcetne zefektivneni prace s nim.




« Poslední změna: 05-05-2020, 09:28:19 od pf1957 »

Offline pepak

  • Padawan
  • ******
  • Příspěvků: 1559
  • Karma: 37
    • Pepak.net
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #9 kdy: 05-05-2020, 14:36:25 »
Nové Delphi dokážou generovat RTTI informace i pro jiné věci než jen published property tříd. Pomocí RTTI je možné popsat třeba taky pole recordů.

Offline pf1957

  • Padawan
  • ******
  • Příspěvků: 3335
  • Karma: 139
    • Verze Delphi: D2007, XE3, DX10
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #10 kdy: 05-05-2020, 17:28:51 »
Nové Delphi dokážou generovat RTTI informace i pro jiné věci než jen published property tříd. Pomocí RTTI je možné popsat třeba taky pole recordů.
Budu ti verit  ;) Ja ty buhvi proc reinkarnovane recordy nevzal na milost a v podstate je ignoruju, jen jsem si vsimal jejich samoucelneho naduzivani.

Nicmene, v kontextu, v jakem to rozebirame tj. ze zpracovava .DFM a v nem prirazeni hodnoty do property napr.
Kód: Delphi [Vybrat]
  1. Xxx.Yyy = zzz
tak pro Xxx typu record to nelze prelozit -> jako hodnotovy typ ho lze prirazovat pouze en bloc tj. Xxx.

Offline Radek Červinka

  • Administrátoři
  • Padawan
  • *****
  • Příspěvků: 3002
  • Karma: 108
    • Verze Delphi: D2007, DXE + 2 poslední
    • O Delphi v češtině
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #11 kdy: 05-05-2020, 20:07:21 »
Budu ti verit  ;) Ja ty buhvi proc reinkarnovane recordy nevzal na milost a v podstate je ignoruju, jen jsem si vsimal jejich samoucelneho naduzivani.

Povime si za dva mesice - doufam, melo to smysl
Embarcadero MVP - Czech republic

Offline age.new

  • Hrdina
  • ****
  • Příspěvků: 316
  • Karma: 0
Re:RTTI - Načtení DFM (FMX) souboru, rozebrání a aplikace
« Odpověď #12 kdy: 06-05-2020, 08:51:24 »
Ten test je jediný způsob, jak zamezit pádu do chyby v případě čtení / zápisu hodnoty do požadované property
Jestlize sam pises, ze RTTI se generuje jen pro published properties,  tak pak je ten text nadbytecny, ne? Najit pres RTTI muzes jedine published property.

Citace
Jak je ale možné, že například property TButton.Text neprojde přes podmínku IsPublishedProp i když je deklarovaná v published sekci?
A co takhle chyba v tom tvem parseru?  ;)

Letmo jsem se na tvuj kod dival a dost se mi to nelibi:

- pozitivni logika a noreni bloku do sebe
- nevyclenene invarianty, takze clovek u rady takovych tech kopirovanych copy(...) nevi, s cim vlastne operuje, kdyz by to trasoval
- ale hlavne: matouci pojmenovani promennych a jejich pouzivani k vice ucelum. Jestli to ctu dobre, tak tam mas chybu prave u toho obj, protoze jakmile se ti vyskytne reference na property.property, tak sice nastavis obj na lokalni instanci referovaneho objektu, ale ztratis tak instanci puvodni komponenty, ktera pak uz napr. property Text skutecne nema. A pokud v .DFM nasleduje odkaz na property komponenty po odkazu na property.property, tak ji to nenajde, takze to spadne,

Moje doporuceni je poradne to prepsat a zejmena udelat poradek v promennych
s -> sLine, jinde s -> sElement
Component -> sCompName
obj -> pouzivat jen skutecne jako anonymni objekt na jednu operaci, jehoz property se hodnota prirazuje a pro component zavest napr. oCompInst a obj spravne reinicializovat -> ted to mas spatne pred cyklem etc... etc...

Taky bys ta jmena mel sjednotit: kdyz pouzivas Madarskou notaci sValue, tak bys ji mel pouzivat vsude nebo nikde (BTW, Madarska notace je prezitek z jazyku bez silne typove kontroly jako ASM a C), symbolicka jmena byvaji v Delphi v CamelCase bez podtrzitek aj. rozhodnout se, jestli budes odlisovat promenne 1. malym pismenem (jako v C-like svete) nebo ne (jako v Delphi tj. obj vs. Obj).

A kazdou promennou pouzivat jen k jedinemu ucelu. Prace je s tim minimalni, v run-time to nic nestoji, protoze je to automaticka promenna na zasobniku, ale srozumitelnost a tim padem i spolehlivost toho kodu se vyznamne zvysi, vcetne zefektivneni prace s nim.

Ano, je to skutečně tak. Chyba je v referenci na Obj objekt. S každou sub-property je třeba opět cyklicky volat proceduru Parse_Line (s drobnými úpravy, protože těch sub-property může být i několik). Možná bych si toho všiml dříve, ale v debug režimu mi proměnná Obj ukazuje jen prázdné závorky i když má správnou referenci.

Děkuji za pomoc.
« Poslední změna: 06-05-2020, 08:54:45 od age.new »