Finally doing some FMX/Androidy stuff (on XE5) and being a noob about what should be obvious it seems:
Finally doing some FMX/Androidy stuff (on XE5) and being a noob about what should be obvious it seems:
What's the right way to hook some custom object reference/data to an FMX.Listview.TListViewItem that was created with e.g. "newItem := AListView.Items.Add;"?
What I've tried: Had a look at the "Objects" property, but that seems to be more to do with specialized pre-defined stuff (e.g. Glyphs, TextButtons, "Accessory" objects) and the "Data" property, which is what I'd have thought would be the way to go. But "Data" is rebuffing my simplistic attempts, and the documentation is not being much help: http://goo.gl/EjkBFZ
Tried directly assigning e.g. newItem.Data['Obj'] := someObj; This compiles but then it doesn't seem like that results in the reference going anywhere where one can get it back from the item, such as in the TListViewItemClick event handler.
Tried using the From() method with someObj as parameter. This also compiles but then as above the reference doesn't seem to be anywhere obvious.
TListViewITem has an "AsObject" property but this is nil (when you've shoved something into the Data property directly as above of via From()) and is also not assignable so you can't just directly put something into it, clearly I must be missing the bleeding obvious.
Can someone give me a shove in the right direction?
Edit: Tried newItem.Data['Obj'] := TValue.From(obj); // uses System.Rtti
Same result, AItem.Data['Obj'].AsObject is nil in ListViewItemClick()
Tried newItem.Data['Obj'].From(TValue.From(obj));
No dice. What am I missing about how these objects want to be used?
Edit2: I've traced the assignment:
newItem.Data['Obj'] := TValue.From(obj);
It seems to be doing more or less what I'd expect -- there's a TValue object created, which appears to contain the object I pass in, and it seems to then be passed to the TListviewItem and put somewhere (TListViewItem.SetData does a "LDataObject := AValue.AsObject;") and all seems fine. But perplexingly when I then get around to trying to get the object back out the AsObject() method of the TListViewItem in question returns nil?
Edit3: Wait -- just had a close look at that method again -- the LDataObject is a local variable, and after picking up the "AsObject" from the AValue passed in, it does exactly nothing with it further!? Just loses the value? What am I missing? Is this a bug? Am I just not understanding how this is meant to work? WTF? (Reference: XE5 Update2, FMX.Listview.pas line 4654-4698)
Edit4: So, after staring at procedure TListViewItem.SetData(const AIndex: string; const AValue: TValue); for a while longer, I came to the tentative conclusion that the code is just wrong, and that the intent of the writer was probably for the "if" code to be "in addition" to the act of storing the value that's been handed to SetData().
So this means that having the line that stores the value inside the (special case) If statement is probably just a mistake/side-effect/refactoring gone wrong somewhere. Consequently i've patched FMX.Listview.pas and moved the value assigment into the FData dictionary to be unconditional (as I suspect it should be). This seems to have fixed my problem and allows my code to correct pick up the object I shoved into the TListViewItem in the natural way I expect.
I hate calling "bug" so quickly (usually perceived library/framework bug is your own code's fault), but this seems like it's clearly just (a rather unsettling) blunder in FMX? Am I wrong?!?
Edit5: Checked on work PC where we have XE8 installed -- same bug, if indeed it is a bug, is present there also... :(
Edit6: For anyone reading this some time in the future: I've reviewed the FMX code and concluded this is a bug. After fixing it, my original code works as expected. I've also submitted an issue on the bug tracker and added some documentation to the non-existent prior documentation for the .Data property of FMX TLiveViewItem in the DocWiki.
http://docwiki.embarcadero.com/Libraries/XE8/en/FMX.ListView.TListViewItem.Data
What's the right way to hook some custom object reference/data to an FMX.Listview.TListViewItem that was created with e.g. "newItem := AListView.Items.Add;"?
What I've tried: Had a look at the "Objects" property, but that seems to be more to do with specialized pre-defined stuff (e.g. Glyphs, TextButtons, "Accessory" objects) and the "Data" property, which is what I'd have thought would be the way to go. But "Data" is rebuffing my simplistic attempts, and the documentation is not being much help: http://goo.gl/EjkBFZ
Tried directly assigning e.g. newItem.Data['Obj'] := someObj; This compiles but then it doesn't seem like that results in the reference going anywhere where one can get it back from the item, such as in the TListViewItemClick event handler.
Tried using the From() method with someObj as parameter. This also compiles but then as above the reference doesn't seem to be anywhere obvious.
TListViewITem has an "AsObject" property but this is nil (when you've shoved something into the Data property directly as above of via From()) and is also not assignable so you can't just directly put something into it, clearly I must be missing the bleeding obvious.
Can someone give me a shove in the right direction?
Edit: Tried newItem.Data['Obj'] := TValue.From(obj); // uses System.Rtti
Same result, AItem.Data['Obj'].AsObject is nil in ListViewItemClick()
Tried newItem.Data['Obj'].From(TValue.From(obj));
No dice. What am I missing about how these objects want to be used?
Edit2: I've traced the assignment:
newItem.Data['Obj'] := TValue.From(obj);
It seems to be doing more or less what I'd expect -- there's a TValue object created, which appears to contain the object I pass in, and it seems to then be passed to the TListviewItem and put somewhere (TListViewItem.SetData does a "LDataObject := AValue.AsObject;") and all seems fine. But perplexingly when I then get around to trying to get the object back out the AsObject() method of the TListViewItem in question returns nil?
Edit3: Wait -- just had a close look at that method again -- the LDataObject is a local variable, and after picking up the "AsObject" from the AValue passed in, it does exactly nothing with it further!? Just loses the value? What am I missing? Is this a bug? Am I just not understanding how this is meant to work? WTF? (Reference: XE5 Update2, FMX.Listview.pas line 4654-4698)
Edit4: So, after staring at procedure TListViewItem.SetData(const AIndex: string; const AValue: TValue); for a while longer, I came to the tentative conclusion that the code is just wrong, and that the intent of the writer was probably for the "if" code to be "in addition" to the act of storing the value that's been handed to SetData().
So this means that having the line that stores the value inside the (special case) If statement is probably just a mistake/side-effect/refactoring gone wrong somewhere. Consequently i've patched FMX.Listview.pas and moved the value assigment into the FData dictionary to be unconditional (as I suspect it should be). This seems to have fixed my problem and allows my code to correct pick up the object I shoved into the TListViewItem in the natural way I expect.
I hate calling "bug" so quickly (usually perceived library/framework bug is your own code's fault), but this seems like it's clearly just (a rather unsettling) blunder in FMX? Am I wrong?!?
Edit5: Checked on work PC where we have XE8 installed -- same bug, if indeed it is a bug, is present there also... :(
Edit6: For anyone reading this some time in the future: I've reviewed the FMX code and concluded this is a bug. After fixing it, my original code works as expected. I've also submitted an issue on the bug tracker and added some documentation to the non-existent prior documentation for the .Data property of FMX TLiveViewItem in the DocWiki.
http://docwiki.embarcadero.com/Libraries/XE8/en/FMX.ListView.TListViewItem.Data
http://docwiki.embarcadero.com/Libraries/XE8/en/Talk:FMX.ListView.TListViewItem.Data
ReplyDeleteDecided to address the fact that the Data property had no documentation.
Using the Tag property with a TObjectList would be one workaround.
ReplyDeleteThanks -- I've already fixed the FMX code and it works fine with the fix. I'm fairly sure now, having carefully reviewed the FMX code, that this is simply a bug due to some kind of refactoring error. I'm in the process of submitting a bug report as we speak.
ReplyDeleteI'm still rather aghast over the fact that such a basic bug ever found its way into the product in the first place, and additionally the fact that this seemingly obvious flaw wasn't quickly spotted, reported and hasn't been fixed a long time ago. It really does make me wonder how much these controls/components are used in production systems and by extension, what else I might discover if I try to use some of the "new" stuff in anger.
https://quality.embarcadero.com/browse/RSP-11591
ReplyDeleteSo I've had a response, and apparently it's not a bug but a feature, and instead of using the seemingly natural "Data" property, which is exposed as a TValue, and which consequently will otherwise store and accept a TObject descendant, you're supposed to shove it into the Tag property via force casting. (But if it's a TBitmap then it's allowed?!)
ReplyDeleteThis about sums up how I feel about this: http://goo.gl/GxEV4S
(I've asked for some clarification on what exactly would break with the change I've proposed but I'm not holding out much hope as I imagine there would be some thing that is affected. Even though even if that's the case, I'd have thought that whatever code is able to process TBitmaps elsewhere in the framework could be made to simply ignore other types of objects so as to allow this interface to work intuitively and as one would naturally expect, and to prevent developers from having to use things like Tags which clearly were not intended to house objects, but what do I know? Why can't things just for once have a nice clean obvious unsurprising API not requiring hacks, casts and kludges?!)
If that is by design that is some fucked up design tbh.
ReplyDeleteStefan Glienke Well the precise comment was "Data field is meant for data binding purposes and it supports assigning either simple values such as numbers or classes descended from TBitmap. Other classes are not supported. This is by design and it has been like that since beginning (XE4)."
ReplyDeletePerhaps I don't understand the ramifications of this (also) being used for data binding etc?