Zakaria 0 Posted October 22 how add property tagString to all delphi Compememet Share this post Link to post
PeterBelow 238 Posted October 22 8 minutes ago, Zakaria said: how add property tagString to all delphi Compememet If you want to be able to edit this new property in the IDE Object Inspector I don't see a way to do this since it would require not only a change of the source code of the TComponent class but also a rebuild of all design and run-time packages that use TComponent, and you do not have the source code for all of them, as far as I know. If using this property in code would be enough there may be a way to fake it using a class helper for TComponent that uses the existing Tag property to store an index of the actual string held in some global container, like a TStringlist. Share this post Link to post
Uwe Raabe 2058 Posted October 22 @PeterBelow It is even possible to inject new properties to the Object Inspector. In Cmon.DataSense.Design.pas I use this technique to add a DataSource and DataField property to supported controls. The additional data is stored in a special component (TDataSense) using a dictionary internally: 4 Share this post Link to post
eivindbakkestuen 47 Posted October 22 1 hour ago, Zakaria said: how add property tagString to all delphi Compememet What problem are you trying to solve? 1 Share this post Link to post
Brandon Staggs 278 Posted October 22 3 hours ago, Zakaria said: how add property tagString to all delphi Compememet The first step is to re-think whatever series of choices led to this abysmal state. 3 Share this post Link to post
Anders Melander 1794 Posted October 22 49 minutes ago, Brandon Staggs said: whatever series of choices led to this abysmal state. *zing* 🔥 Share this post Link to post
Remy Lebeau 1402 Posted October 22 7 hours ago, Zakaria said: how add property tagString to all delphi Compememet You can't add a new property to all components. But, the existing Tag property is a NativeInt, so it can hold a String (which is just a pointer under the hood), eg: var tagStr: NativeInt := 0; String(Pointer(tagStr)) := SomeString; SomeComponent.Tag := tagStr; ... var tagStr := SomeComponent.Tag; SomeString := String(Pointer(tagStr)); Just make sure you release the String properly before the Component is destroyed, eg: var tagStr := SomeComponent.Tag; String(Pointer(tagStr)) := ''; 2 Share this post Link to post
Brandon Staggs 278 Posted October 22 17 minutes ago, Remy Lebeau said: You can't add a new property to all components. But, the existing Tag property is a NativeInt, so it can hold a String (which is just a pointer under the hood), eg: var tagStr: NativeInt := 0; String(Pointer(tagStr)) := SomeString; SomeComponent.Tag := tagStr; ... var tagStr := SomeComponent.Tag; SomeString := String(Pointer(tagStr)); Just make sure you release the String properly before the Component is destroyed, eg: var tagStr := SomeComponent.Tag; String(Pointer(tagStr)) := ''; What could possibly go wrong 8 Share this post Link to post
Remy Lebeau 1402 Posted October 23 9 hours ago, Brandon Staggs said: What could possibly go wrong 😎 2 Share this post Link to post
Jim McKeeth 106 Posted October 23 (edited) This might be an even worse idea.... {$IFDEF Win64} type TBadIdea = array [0..7] of AnsiChar; {$ENDIF} {$IFDEF Win32} type TBadIdea = array [0..3] of AnsiChar; {$ENDIF} function ReadTagString(Tag: NativeInt): String; var BadIdea: TBadIdea absolute Tag; begin Result := string(BadIdea); end; function WriteTagString(Tag: String): NativeInt; var BadIdea: TBadIdea absolute Result; begin for var i := 1 to SizeOf(NativeInt) do if i <= Length(Tag) then BadIdea[i-1] := AnsiChar(Tag[i]) else BadIdea[i-1] := #0; end; procedure TFormEvenWorse.btnSetClick(Sender: TObject); begin Tag := WriteTagString(edtValue.Text); edtValue.Text := ''; end; procedure TFormEvenWorse.btnReadClick(Sender: TObject); begin edtValue.Text := string(ReadTagString(Tag)); end; There should be better handling of Ansi vs Unicode strings. Technically if you are only going to store characters you could make more efficient use of the bytes. Edited October 23 by Jim McKeeth Share this post Link to post
Jim McKeeth 106 Posted October 23 17 hours ago, Uwe Raabe said: @PeterBelow It is even possible to inject new properties to the Object Inspector. In Cmon.DataSense.Design.pas I use this technique to add a DataSource and DataField property to supported controls. The additional data is stored in a special component (TDataSense) using a dictionary internally: This is the right answer. You can see an example of this when you put a component on a TRelativePanel. Notice how there are additional properties after Width in the Object Inspector... Share this post Link to post
David Heffernan 2347 Posted October 23 If the OP would explain the original motivation behind this, then we could propose proper solutions to the actual problem. This is a canonical XY question. 1 Share this post Link to post
Remy Lebeau 1402 Posted October 23 (edited) 42 minutes ago, Jim McKeeth said: This might be an even worse idea. Very bad idea. ReadTagString() is not so bad, but IDK what WriteTagString() is trying to accomplish. It is converting characters into bytes, completely ignoring the pointer that is inside the string. It is not the inverse of ReadTagString(). Edited October 23 by Remy Lebeau Share this post Link to post
Jim McKeeth 106 Posted October 23 (edited) 33 minutes ago, Remy Lebeau said: IDK what WriteTagString() is trying to accomplish. It is converting characters into bytes, completely ignoring the pointer that is inside the string. Convert the string into an array of AnsiChar, which is just an array of bytes, which is absolute to a NativeInt.... type TBadIdea = array [0..7] of AnsiChar; So there is no pointer.... It is bytes all the way down.... Edited October 23 by Jim McKeeth Share this post Link to post
Kas Ob. 121 Posted October 23 I think the best way is to have a singleton, that initialized very early in the project (preferably right after MM and before forms), this singleton will hook TComponent.Create and Destroy, grabbing its address (value/pointer) and add it to internally managed list, this list have the component associated with an extra data, extra data could be a string, integer, record.. and even classes. This will solve/remove the need to manually managing this extra data (eg. string), also ensure memory integrity for this extra data without interfering with the associated component in anyway, on top of that helper(s) for specific components can be used for easier and simplified use. Share this post Link to post
John R. 18 Posted October 23 I sometimes need to use the "Tag" property to store a string, and I've noticed that the number of instances and changes are minimal so I could store them in memory during the application lifetime. That's why I've opted with a global singleton which: Returns a string object when asked, and keeps the instance in memory while the application is running Releases every instances once the application is closed Even if this is not an optimal solution, this is the "cleanest" way I could think of at the time. Something like: uses Spring.Collections; // ... type TStrObj = class(TObject) private FValue: string; public constructor Create(const aValue: string = ''); property Value: string read FValue write FValue; end; type FStrObjects: IList<TStrObj>; // ... constructor TSomeSingleton.Create(); begin FStrObjects := TCollections.CreateObjectList<THndStrObj>(True); end; function TSomeSingleton.GetStringObject(const aValue: string): TStrObj; begin // Create the object Result := TStrObj.Create(aValue); // Add it to the list FStrObjects.Add(Result); end; // ... // Usage: SomeComponent.Tag := NativeInt(Pointer(TSomeSingleton.GetStringObject('Some String Value'))); Share this post Link to post
PeterBelow 238 Posted October 23 On 10/22/2024 at 12:52 PM, Uwe Raabe said: @PeterBelow It is even possible to inject new properties to the Object Inspector. In Cmon.DataSense.Design.pas I use this technique to add a DataSource and DataField property to supported controls. The additional data is stored in a special component (TDataSense) using a dictionary internally: Good to know one can learn something new even at my age :)... 2 Share this post Link to post
Remy Lebeau 1402 Posted October 23 (edited) 12 hours ago, Jim McKeeth said: Convert the string into an array of AnsiChar, which is just an array of bytes, which is absolute to a NativeInt.... So there is no pointer.... True. Now I see what the code is doing. It's stuffing the first 4 (32bit) or 8 (64bit) characters of the string directly into the NativeInt's bytes. So, if the string is longer than the NativeInt can hold, the remaining characters get lost. Storing the string's pointer into the NativeInt would preserve the entire string, but at the cost of having to manage its reference count properly. Edited October 23 by Remy Lebeau Share this post Link to post
Lars Fosdal 1792 Posted October 24 I've gone even deeper down the rabbit whole and referenced object instances through the tag... I am not proud of it. Share this post Link to post
Remy Lebeau 1402 Posted October 24 19 minutes ago, Lars Fosdal said: I've gone even deeper down the rabbit whole and referenced object instances through the tag... I am not proud of it. That is actually quite common, and is why the Tag is a NativeInt to begin with. Share this post Link to post
Lars Fosdal 1792 Posted October 24 "rabbit whole" ... /sigh Quote That is actually quite common, and is why the Tag is a NativeInt to begin with. Yeah, I guess it is... still, it is a far cry from clean code. Share this post Link to post
Anders Melander 1794 Posted October 24 36 minutes ago, Remy Lebeau said: why the Tag is a NativeInt to begin with Except it wasn't... Prior to XE2 it was an integer. Share this post Link to post
Uwe Raabe 2058 Posted October 24 47 minutes ago, Anders Melander said: Except it wasn't... Prior to XE2 it was an integer. Probably because at that time pointers were still 32 bit only. Share this post Link to post
Anders Melander 1794 Posted October 24 1 minute ago, Uwe Raabe said: Probably because at that time pointers were still 32 bit only. The point was that since it was originally (refs "to begin with") declared as an integer, not a nativeint, the original intent obviously wasn't that it could be used for pointers. It just so happened that it could. Share this post Link to post
Remy Lebeau 1402 Posted October 24 5 hours ago, Anders Melander said: The point was that since it was originally (refs "to begin with") declared as an integer, not a nativeint, the original intent obviously wasn't that it could be used for pointers. It just so happened that it could. I'll rephrase. Prior to the introduction of 64bit, the Tag was an Integer and pointers fit in an Integer, so it was common to store pointers in a Tag. When 64bit was added, the Tag became a NativeInt to preserve existing code in common use. 1 Share this post Link to post