aehimself 396 Posted February 22, 2021 Hello, I have a method that hides, rearranges and resizes components based on some conditions runtime. All is working fine, but the question eventually arrived: is is possible to "reset" everything to it's original (design time) state? Instead of creating a list of all changed components (and it's properties) I'm wondering if it's possible to re-read and re-apply the .DFM settings to a number of selected components? A list would be easier, but the DFM streaming seems to be the "professional" way to do it. I guess I'll need a TReader for that but I have absolutelly no idea how that works 🙂 Share this post Link to post
Attila Kovacs 629 Posted February 22, 2021 There are the Explicit* properties in the dfm which are storing the design time positions and sizes. If you are not striping them. Share this post Link to post
M.Joos 30 Posted February 22, 2021 I guess it is theoretically possible to shoehorn Delphi's streaming mechanism to do what you want. Essentially you could just reread your form with an adjusted TReader. Specifically you would not want for the components to be recreated. Have a look at the TReader events, specifically TReader.OnCreateComponent One problem though is how to "skip" rereading of components as you want to only select a number of components. 1 Share this post Link to post
Uwe Raabe 2058 Posted February 22, 2021 What about creating a new form instance and adjust the components and properties in question from that? 2 Share this post Link to post
Stano 143 Posted February 22, 2021 A Tag can be used for this purpose. Unless it is used for something else. Share this post Link to post
microtronx 38 Posted February 22, 2021 (edited) 5 hours ago, aehimself said: I have a method that hides, rearranges and resizes components based on some conditions runtime. All is working fine, but the question eventually arrived: is is possible to "reset" everything to it's original (design time) state? What about only creating your form without applying the saved changes? = That should be your design time state, or I'm wrong? Edited February 22, 2021 by microtronx Share this post Link to post
aehimself 396 Posted February 22, 2021 6 hours ago, M.Joos said: Specifically you would not want for the components to be recreated. Have a look at the TReader events, specifically TReader.OnCreateComponent I already started to experiment with the TReader component, unfortunately OnCreateComponent will only let me to use a different constructor other than the default. If the component is not created in the handler it will be created anyway. This thing is still a mess to me though... how do I specify WHERE the new components will be created...? As it needs a stream I need to get it from the executable - I guess it will be stored as a resource...? I'm only at the beginning. I'll find the answers soon enough. 6 hours ago, Uwe Raabe said: What about creating a new form instance and adjust the components and properties in question from that? This is actually a really neat idea. As the TReader method will create the components anyway I suppose the memory usage will be close to equal anyway. As a huge bonus, I know how to create a new form instance 🙂 3 hours ago, Stano said: A Tag can be used for this purpose. Unless it is used for something else. An other good idea. Tags are - unfortunately though - already in use in said project 😞 I'll dig into the TReader a little bit so I'll have a basic understanding of how it works / what it does. It never hurts to know something, you never know when you'll need such a thing. Thank you all for your answers! Share this post Link to post
M.Joos 30 Posted February 22, 2021 7 minutes ago, aehimself said: I already started to experiment with the TReader component, unfortunately OnCreateComponent will only let me to use a different constructor other than the default. If the component is not created in the handler it will be created anyway. This thing is still a mess to me though... how do I specify WHERE the new components will be created...? As it needs a stream I need to get it from the executable - I guess it will be stored as a resource...? I'm only at the beginning. I'll find the answers soon enough. This is actually a really neat idea. As the TReader method will create the components anyway I suppose the memory usage will be close to equal anyway. As a huge bonus, I know how to create a new form instance 🙂 An other good idea. Tags are - unfortunately though - already in use in said project 😞 I'll dig into the TReader a little bit so I'll have a basic understanding of how it works / what it does. It never hurts to know something, you never know when you'll need such a thing. Thank you all for your answers! For inspiration you may also look into this excellent answer from Uwe Raabe on Stackoverflow on a related question: https://stackoverflow.com/questions/47347578/manually-skip-a-component-from-being-created-from-the-dfm which also shows a hacky way to prevent components from getting created. I guess Uwe wasn't aware of his answer more than 3 years ago 😉 As for loading the dfm from the resource that's in the executable have a look at http://docwiki.embarcadero.com/Libraries/Sydney/de/System.Classes.TStream.ReadComponentRes 1 Share this post Link to post
Attila Kovacs 629 Posted February 22, 2021 I think this is what you need, you just have to adjust it a bit. // search in dfm resources by Attila Kovacs unit DFMSearch; interface uses System.Classes, System.Generics.Collections; type TDFMSearch = class private FFormClasses: TList<string>; FResourceNames: TList<string>; FSearchDictionary: TDictionary<string, string>; function GetFormClasses(AClass: TClass): TList<string>; procedure BuildStrings(AResources: TList<string>); procedure EnumClasses(AClass: TClass); public function Find(const ASearchString: string): TList<string>; constructor Create(AClass: TClass); overload; constructor Create(AClassNames: TList<string>); overload; destructor Destroy; override; end; implementation uses Winapi.Windows, System.Rtti, System.SysUtils, System.StrUtils; function EnumResNames(Module: HMODULE; ResType, ResName: PChar; Obj: Pointer): integer; stdcall; var ds: TDFMSearch; begin ds := TDFMSearch(Obj); if ds.FFormClasses.IndexOf(string(ResName)) > -1 then ds.FResourceNames.Add(string(ResName)); Result := 1; end; function TDFMSearch.GetFormClasses(AClass: TClass): TList<string>; var cl: TClass; ctx: TRttiContext; types: TArray<TRttiType>; typ: TRttiType; begin Result := TList<string>.Create; ctx := TRttiContext.Create; types := ctx.GetTypes; for typ in types do begin if typ.TypeKind = tkClass then begin cl := typ.AsInstance.MetaclassType; if (cl <> AClass) and cl.InheritsFrom(AClass) then Result.Add(UpperCase(cl.ClassName)); end; end; end; procedure TDFMSearch.BuildStrings(AResources: TList<string>); var i: integer; Flags: TFilerFlags; R: TReader; RS: Tresourcestream; vt: TValueType; Position: integer; text, PropName, CompClass, CompName, StrValue, ResStr: string; procedure ReadProperty; begin PropName := R.ReadStr; if ContainsText(PropName, 'caption') then begin vt := R.NextValue; case vt of vaWString, vaUTF8String: StrValue := R.ReadString; vaString, vaLString: StrValue := R.ReadString; else R.SkipValue; end; text := text + ' ' + StrValue; end else R.SkipValue; end; procedure ReadData; forward; procedure ReadComponent; begin R.ReadPrefix(Flags, Position); CompClass := R.ReadStr; CompName := R.ReadStr; ReadData; end; procedure ReadData; begin while not R.EndOfList do ReadProperty; R.ReadListEnd; while not R.EndOfList do ReadComponent; R.ReadListEnd; end; begin for i := 0 to AResources.Count - 1 do begin ResStr := UpperCase(AResources[i]); RS := Tresourcestream.Create(HInstance, ResStr, RT_RCDATA); try R := TReader.Create(RS, 4096); try text := ''; R.ReadSignature; ReadComponent; FSearchDictionary.AddOrSetValue(ResStr, text); finally R.Free; end; finally RS.Free; end; end; end; constructor TDFMSearch.Create(AClass: TClass); begin FSearchDictionary := TDictionary<string, string>.Create; EnumClasses(AClass); end; constructor TDFMSearch.Create(AClassNames: TList<string>); begin FSearchDictionary := TDictionary<string, string>.Create; BuildStrings(AClassNames); end; destructor TDFMSearch.Destroy; begin FSearchDictionary.Free; inherited; end; procedure TDFMSearch.EnumClasses(AClass: TClass); begin if not Assigned(FSearchDictionary) then begin FFormClasses := GetFormClasses(AClass); try FResourceNames := TList<string>.Create; try EnumResourceNames(HInstance, RT_RCDATA, @EnumResNames, NativeInt(Self)); BuildStrings(FResourceNames); finally FResourceNames.Free; end; finally FFormClasses.Free; end; end; end; function TDFMSearch.Find(const ASearchString: string): TList<string>; var i, j, slen: integer; sl: TArray<string>; pa: TPair<string, string>; begin Result := TList<string>.Create; sl := ASearchString.Split([' ']); slen := Length(sl); for pa in FSearchDictionary do begin j := 0; for i := 0 to High(sl) do begin if ContainsText(pa.Value, sl[i]) then inc(j); end; if j = slen then Result.Add(pa.Key); end; end; end. 1 Share this post Link to post
balabuev 102 Posted February 23, 2021 21 hours ago, aehimself said: I'm wondering if it's possible to re-read and re-apply the .DFM settings to a number of selected components This is not technically possible with TReader. However, I want to mention one different aspect: Such a solution will not be a kind of "professional" solution anyway, because controls are not developed having multiple re-loads in mind. Control's code may use different load process related flags, like csReading, csLoading and may override Loaded method. And the developer may (which is often the case) rely on the fact that the loading happens right after creation. 1 Share this post Link to post
Attila Kovacs 629 Posted February 23, 2021 56 minutes ago, balabuev said: This is not technically possible with TReader. Re-read with TReader -> yes, see my previous comment Re-apply with TReader -> no, but you can reapply it by updating the corresponding properties by yourself Share this post Link to post
balabuev 102 Posted February 23, 2021 (edited) 1 hour ago, Attila Kovacs said: Re-read with TReader -> yes, see my previous comment Well, if you use only utility methods, like ReadStr, ReadSignature, etc - then yes, you right. But this way, you need to rewrite most other high level reading code manually. Edited February 23, 2021 by balabuev Share this post Link to post
Attila Kovacs 629 Posted February 23, 2021 2 minutes ago, balabuev said: But this ways, you need to rewrite most other high level reading code manually. If there are some non-standard written controls which has to be restored, yes, you are right, it would need it's reader. On standard controls you can skip the data you don't need or you don't know. We can't see this from here. Nor the actual software to be able to decide which would be less effort, storing local the initial values or reading the dfm. I wanted to show him how to read the resources by its classtypes and read some properties, which he asked. Share this post Link to post
Pat Foley 51 Posted February 23, 2021 Quote There are the Explicit* properties in the dfm which are storing the design time positions and sizes. How to use Explicit properties to restore positions? Iteration or Is there simple revert message undocumented somewhere. Share this post Link to post
balabuev 102 Posted February 23, 2021 4 minutes ago, Pat Foley said: How to use Explicit properties to restore positions? No reasonable way. ExplicitXXX [pseudo]properties are a part of align subsystem data. And they are not actually always stored in dfm. Share this post Link to post
Uwe Raabe 2058 Posted February 23, 2021 The problem with those ExplicitXXX values is that they are synched with their current XXX counterparts on several occasions. These are WM_SIZE, WM_MOVE and SetBounds. You cannot assume that these values contain the original DFM content, even if that was the case after loading the form. 1 Share this post Link to post
balabuev 102 Posted February 23, 2021 (edited) The problem with storing/restoring positions is even more general. If some form have child controls, which use Align and/or Anchors features, then any attempt to reset control positions via iterating the controls and assignings original desing-time positions - is an incorrect way, which may end up with the invalid layout. Especially (but it's not required) when the form is resizeable. Edited February 23, 2021 by balabuev Share this post Link to post
Pat Foley 51 Posted February 23, 2021 Thanks. would creating a new form be good solution for this case. Share this post Link to post
Attila Kovacs 629 Posted February 23, 2021 @balabuev TWinControl.DisableAlign may help there Share this post Link to post
balabuev 102 Posted February 23, 2021 (edited) 9 minutes ago, Attila Kovacs said: TWinControl.DisableAlign may help there Will not be sufficient, especially if we want to treat only selected controls, as topic starter initially mentioned. And in the case when parent form (or control) was already resized. Edited February 23, 2021 by balabuev Share this post Link to post
Attila Kovacs 629 Posted February 23, 2021 He also mentions that he is successfully hides/resizes/rearranges those components. That said, I can't see any problem to reset them. Also, I trust him fully that he knows what he is doing. Share this post Link to post
balabuev 102 Posted February 23, 2021 1 minute ago, Attila Kovacs said: He also mentions that he is successfully hides/resizes/rearranges those components Manual explicit rearrangement is somewhat different from the formal restoring algorithm (like re-reading from dfm). Anyway, I just highlight possible issues, which I beleive are not intuitive from the first look. May be I complicate things compared to what is practically needed in this particular case. Share this post Link to post
Guest Posted February 23, 2021 On 2/22/2021 at 12:11 PM, aehimself said: I'm wondering if it's possible to re-read and re-apply the .DFM settings to a number of selected components? Nope. I had this discussion with DevEx support. I wanted to implement a "grid settings reset button". After a lot of pondering; the "contract" that the various components has with the DFM is linked in to the code reading it too. Essentially you cannot change the DFM resource w/o recompiling (i may have to specify that). If you "reload" at a different state, you will open a can of worms. I have "reverted" to having a "Debug Persistance Writer" function as a part of my application. It essentially writes down the properties of [pertinent] component(s) before anything really happens. Reading THAT (either using a persistance lib, plain functions or using the components own "persistance" handling admittedly feel unoptimized (first read DFM, then read .ini) but it works. My end-users can now click "Reset" for any complex DevEx component. With some synchronization (not threads) i can restore a complete frame runtime with DB-awares connected. Quick comment, HTH, /Dany Share this post Link to post
balabuev 102 Posted February 23, 2021 4 hours ago, Dany Marmur said: If you "reload" at a different state, you will open a can of worms. This is what I've initially talked about. Share this post Link to post
Fr0sT.Brutal 900 Posted February 24, 2021 On 2/22/2021 at 2:11 PM, aehimself said: I have a method that hides, rearranges and resizes components based on some conditions runtime. All is working fine, but the question eventually arrived: is is possible to "reset" everything to it's original (design time) state? Instead of creating a list of all changed components (and it's properties) I'm wondering if it's possible to re-read and re-apply the .DFM settings to a number of selected components? If you only want to restore Visible and Bounds, then saving original props in a list is the simplest option. Share this post Link to post