- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
EntityView2 IBindingList differences to BindingList<T>
Joined: 01-Feb-2006
Answering out of order:
I don't follow. If I have 100 items in the view, and I sort it on a different property so all rows are at a different location, I can't use 1 ItemMoved, I have to use a Reset. That's why the reset is issued. (similar to what DataView does IIRC)
That is a different scenario to the one we were originally discussing. Changing the sorted property should indeed sent a Reset (unless that change happened to result in exactly the same ordering but detecting that is probably an optimization too far).
The scenario we were discussing was an ItemChanged on the source list the view is listening to. In that case (and assuming for now we are just discussing a View with a Sort property - ie not a Filter which may add/remove the item from the Views scope), the changed item will either stay in the same position or it will move to another position within the sorted View. It is the latter scenario I was suggesting that an ItemMoved would be appropriate.
if I sort a view on a property, every row could be placed at a new index, which means every row has been moved to a new location, which means for every row an ItemMoved has to be raised (as it is per item). Therefore reset is better.
I disagree that reset is better for the ItemChanged scenario. Whilst it is true that every row could be placed at a new index, it is perhaps better described that every item between the original position and the new position has had its index changed. An ItemMoved contains all the information needed for a listener to determine what to do.
Taking your example of 100 items in a view, all sorted. The item at index 70 changes resulting in it being moved to index 60. This means that items at indexes 0 to 59 have not changed; index[60] now contains the item that was changed; the items at indexes 61 to 70 have moved down one place and items at indexes 71 to 99 have not changed. If a grid was bound to the View, displaying the first 30 items, on receiving an ItemMoved with OldIndex=70 and NewIndex=60, it could just ignore the message because it does not affect its display at all. If it receives a Reset, it must reread all 30 items. If the grid happened to be displaying items within the changed range, it has all the information it needs within the ItemMoved message to make the adjustment without too much difficulty: item at NewIndex has moved, all items between OldIndex and NewIndex will have moved down by one index (when NewIndex < OldIndex) or moved up (when NewIndex > OldIndex).
For the int it doesn't matter indeed, but for an object it really does matter, it's something else. Maybe you don't want to make that distinction but I do. The docs aren't clear about that, at least not for me, I find them ambiguistic (and BindingList proves that as it uses OldIndex to signal replace vs. property change, which isn't documented)
Why does it matter? Sure it would be nice to know the difference as you may be able to optimize processing. If you knew it was down to a PropertyChange and that property was not involved in the Sort or Filter, you could reduce the processing needed (and just pass on the ItemChanged for listeners). But, until BindingList came along, it has not been possible to tell the difference so you do the same processing anyway and it really doesn't matter whether the item is a new one or the old one but with a property changed - you still just pass the item to your filtering and sorting routines.
Incidentally, I'm confused here, on one hand, you are saying you need to know the distinction (and I am arguing that its a nice to know but not a necessity) and on the other hand, ListChangedEventArgs has support for passing in a PropertyDescriptor to give you exactly the information you want but you are not interested in using it anyway as its not documented.
Here is a list of all the combinations I can think of which could result in an ItemChanged: Non object, item replaced (eg the int example) object, item replaced object, single property changed (used in Filter or Sort) object, single property changed (not used in Filter or Sort) object, multiple properties changed (used in Filter or Sort) object, multiple properties changed (not used in Filter or Sort)
Whilst you might be able to process differently for the single property changed scenarios, unless the source happened to support passing PropertyDescriptor and you actively check that a PropertyDescriptor is present in ListChangedEventArgs, it is my belief that you are unable to tell the difference between any of these scenarios and so must process them all in the same way.
Cheers Simon
simmotech wrote:
Answering out of order:
I don't follow. If I have 100 items in the view, and I sort it on a different property so all rows are at a different location, I can't use 1 ItemMoved, I have to use a Reset. That's why the reset is issued. (similar to what DataView does IIRC)
That is a different scenario to the one we were originally discussing. Changing the sorted property should indeed sent a Reset (unless that change happened to result in exactly the same ordering but detecting that is probably an optimization too far).
The scenario we were discussing was an ItemChanged on the source list the view is listening to. In that case (and assuming for now we are just discussing a View with a Sort property - ie not a Filter which may add/remove the item from the Views scope), the changed item will either stay in the same position or it will move to another position within the sorted View. It is the latter scenario I was suggesting that an ItemMoved would be appropriate.
Sure, though that's an optimization for an edge case which requires specific code: the sorter is applied and it has to check whether a single row changed position. If so, it issues an itemmoved, otherwise a reset. Sorry but that sounds like complex code for an edge case which I'm not going to invest time in at this moment.
Furthermore, setting an indexer to a different instance isn't a 'move': the original item didn't move, it was replaced and that item is now placed at a different location. so you could argue that at the original index an item was removed and at the new index an item was inserted. But I digress...
if I sort a view on a property, every row could be placed at a new index, which means every row has been moved to a new location, which means for every row an ItemMoved has to be raised (as it is per item). Therefore reset is better.
I disagree that reset is better for the ItemChanged scenario.
I was talking about the general sort operation. for the edge case it might be more 'optimal' if a special codepath was created. However I'm not going to add that as that time is better spend on things which are currently also less optimal and not an edge case.
Whilst it is true that every row could be placed at a new index, it is perhaps better described that every item between the original position and the new position has had its index changed. An ItemMoved contains all the information needed for a listener to determine what to do.
Taking your example of 100 items in a view, all sorted. The item at index 70 changes resulting in it being moved to index 60. This means that items at indexes 0 to 59 have not changed; index[60] now contains the item that was changed; the items at indexes 61 to 70 have moved down one place and items at indexes 71 to 99 have not changed. If a grid was bound to the View, displaying the first 30 items, on receiving an ItemMoved with OldIndex=70 and NewIndex=60, it could just ignore the message because it does not affect its display at all. If it receives a Reset, it must reread all 30 items. If the grid happened to be displaying items within the changed range, it has all the information it needs within the ItemMoved message to make the adjustment without too much difficulty: item at NewIndex has moved, all items between OldIndex and NewIndex will have moved down by one index (when NewIndex < OldIndex) or moved up (when NewIndex > OldIndex).
yes, but determining that requires investigation of every element index before and after the sort, and then deciding what to do. One could also say: a reset suggests the order isn't said to be defined, and a listener should investigate if every element is still at a given spot, to optimize its own performance.
Joined: 01-Feb-2006
Furthermore, setting an indexer to a different instance isn't a 'move': the original item didn't move, it was replaced and that item is now placed at a different location. so you could argue that at the original index an item was removed and at the new index an item was inserted. But I digress...
Well there are two points of view of the change here.
From the source List point of view, there is no move involved at all: All the List does is tell listeners that "the item currently residing at index x has changed". Nothing has been placed at a different location from the List's point of view.
From the View point of view, all it knows is what the source List told it. It will then publish an event of its own describing what changed (if anything) in its 'virtual list'. For our scenario (with no filter which could Add or Remove and a Sorter), this means the View ends up resorting 'just in case' the item (regardless of new or old) would end up at a different index and then issuing a Reset because it doesn't know whether it really did move or not.
yes, but determining that requires investigation of every element index before and after the sort, and then deciding what to do. One could also say: a reset suggests the order isn't said to be defined, and a listener should investigate if every element is still at a given spot, to optimize its own performance.
A Reset has an explicit meaning according to MS. Something along the lines of "much data has changed, reread the entire list". A Grid listening to a View could decide not to fully trust the message they have been passed and try to do as you say but they will assume the item they are listening to knows best and just reread all the data.
So image this scenario, you iterate through a large EntityCollection and say sets a flag property on each entity. There happens to be a grid bound to the DefaultView which has a Sorter set (not on the changing property). Each change to an entity will result in the View telling the Grid to Reset which then rereads all of the items it is currently displaying and may provide an alternate sort/filter etc. If you had 1000 entities and the grid was displaying 50 rows can you imaging how slow that is. An that would happen even if the property being changed was not part of the View's Sorter and even if the Grid was not displaying that property!! (I know most grids have a BeginUpdate/EndUpdate but it would be nice for the flag setting routine or whatever called it not to care whether it is bound or not)
If the v3 EntityCollection used the new-style PropertyDescriptor notifications, the v3 EntityView could see that that property was not involved in the Sorter therefore its position cannot have changed and it can just pass on the ItemChanged as-is. The Grid then gets that Item changed and sees that none of its columns use that property and so does nothing. Two relatively tiny changes making a big performance impact.
Cheers Simon
PS Have a good weekend!
simmotech wrote:
Furthermore, setting an indexer to a different instance isn't a 'move': the original item didn't move, it was replaced and that item is now placed at a different location. so you could argue that at the original index an item was removed and at the new index an item was inserted. But I digress...
Well there are two points of view of the change here.
From the source List point of view, there is no move involved at all: All the List does is tell listeners that "the item currently residing at index x has changed". Nothing has been placed at a different location from the List's point of view.
From the View point of view, all it knows is what the source List told it. It will then publish an event of its own describing what changed (if anything) in its 'virtual list'. For our scenario (with no filter which could Add or Remove and a Sorter), this means the View ends up resorting 'just in case' the item (regardless of new or old) would end up at a different index and then issuing a Reset because it doesn't know whether it really did move or not.
I'm not sure if you're debating yourself now but I didn't mention itemmoved to be an option at all.
The view applies the filter on new items to see whether the item matches the filter and if so, it's allowed, if not, it's not accepted. The sorter is applied after this.
It indeed doesn't know if something changed due to the sorter, it doesn't really care. All it knows is that something sorted and therefore its contents might have changed, so it issues a reset. The dataview does this too btw.
yes, but determining that requires investigation of every element index before and after the sort, and then deciding what to do. One could also say: a reset suggests the order isn't said to be defined, and a listener should investigate if every element is still at a given spot, to optimize its own performance.
A Reset has an explicit meaning according to MS. Something along the lines of "much data has changed, reread the entire list". A Grid listening to a View could decide not to fully trust the message they have been passed and try to do as you say but they will assume the item they are listening to knows best and just reread all the data.
(warning, rant ahead (not against you, no worries))
Simon, you have to understand that control vendors are dragging their feet for years to make their controls work with normal databinding code and during the years I've dealt with so many issues that I'm far beyond the point where I'd spend any minute to optimize something which helps them cover up their slow code. It's been enough.
What I could do is add a check whether the list of indexes is different after a sort / filter and if not, don't raise an event. That's as far as I'm willing to go here. (and this could lead to simple code which is general purpose and causing efficient logic, though I've to look at it in detail when I'm working on this item).
I'm not going to spend time on code to optimize a slow grid or other control response. In the past year I'm now working on v3 I've spend a lot of time optimizing code working with UI controls simply the UI controls were so damn slow. Imagine this: I have full undo/redo now, and in a buttonbar I have two buttons: an undo button and a redo button. Every time a command is issued I raised an event which simply managed the state of these buttons. You know, this simple action slowed down operations noticeably, so I had to implement weird code which disabled the buttons during processes. I'm not kidding. Simply enabling/disabling a button! And this was so noticeable that an operation which otherwise would take almost no time at all (as in: instantly) took a second or two or more.
I'm not going to mention the 3rd party control vendor these button bar is from, as I've ran into issues with other vendor's controls as well to a point where they're too stubborn to even implement a simple property which would make my life much easier, and instead want me to recall resize/reformat code over and over again which is considerably slower.
So, I agree with you as I said before, that there are cases where things could be more optimal. However I'm not going to invest any reasonable time on optimizing code for 3rd party control vendors, simply because THEY should do their job properly as well. If my code does something stupid in cases which are very common, no problem, I'll dig in and fix it the same day, everyone on this forum knows that. However there's a line I'll draw and if I have to spend time on this vs. spending time on for example add code to the linq provider to make nested queries work in some more edge cases than they're now, I'll definitely do the latter.
If the v3 EntityCollection used the new-style PropertyDescriptor notifications, the v3 EntityView could see that that property was not involved in the Sorter therefore its position cannot have changed and it can just pass on the ItemChanged as-is. The Grid then gets that Item changed and sees that none of its columns use that property and so does nothing. Two relatively tiny changes making a big performance impact.
There's a tiny problem: the sorter is a SortExpression and doesn't know about property descriptors AND it can sort on multiple columns, not possible with vanilla databinding code / IBindingList sorters. So all there's is the list of indexes which differs after a sort and should be checked. This thread is linked in the issue to solve the item replace thingy, so I'll run into this debate anyway. I'll see if I can manage to write an index check in an hour or so to optimize this. That's as far as I'll go for the gentle(wo)men control vendors.
PS Have a good weekend!
You too!
btw, last sunday I implemented a credits scroller blended into the about dialogbox for v3, with all the names of contributers to the code, including yours.
Joined: 01-Feb-2006
I know I've gone on a bit but you're probably used to me by now (well at least you haven't kicked me off the forums yet)
I understand your reluctance about adding special code for UI controls. I just think that the automatic-sending-of-a-Reset example doesn't give the control a fair chance to optimize itself. Thanks for considerering it though. I was going to offer to have a look myself (and still will if you prefer to spend the time on other things).
Interesting about your issue with maintaining enabled state on buttons (presumably the undo/redo buttons are shared between the processes?). I came up with a scheme for this a while ago and now we are using it in anger, it has worked out really well: Each toolbar has a set of actions bound to the buttons; each process defines its own set of actions; each action, GUI or process, has a string name. Using the Application idle event, the current 'path' of active processes (defined as from the main form down to the current active control) gets to update its Action states. The GUI Actions then instantly reflect the states (Visible/Enabled etc) of whatever matching Action (by name) were found in that path. The GUI Actions have no knowlege of any processes; the process Actions have no knowledge of any GUI controls. Everything just happens like magic.
The SortExpression can determine the names of the Fields is it sorting on can't it? The PropertyDescriptor has a string Name available. If the propertyDescriptor.Name is not used in any of the Fields from the SortExpression, then the sorting order can't have changed. Thats where I was going with that.
Cheers Simon
PS Thanks for the mention in the credit scroller!
simmotech wrote:
I know I've gone on a bit but you're probably used to me by now
(well at least you haven't kicked me off the forums yet)
Oh no worries, I won't kick someone off the forums for being passionate about software development, on the contrary . (in fact, the only people who got banned during the years are the 1 shot spam accounts created by spammers)
I understand your reluctance about adding special code for UI controls. I just think that the automatic-sending-of-a-Reset example doesn't give the control a fair chance to optimize itself. Thanks for considerering it though. I was going to offer to have a look myself (and still will if you prefer to spend the time on other things).
It won't take a lot of code I guess, so I'll give it a shot. Not a lot of time though, as the runtime lib has currently a list of over a 100 feature requests for v3.
Interesting about your issue with maintaining enabled state on buttons (presumably the undo/redo buttons are shared between the processes?). I came up with a scheme for this a while ago and now we are using it in anger, it has worked out really well: Each toolbar has a set of actions bound to the buttons; each process defines its own set of actions; each action, GUI or process, has a string name. Using the Application idle event, the current 'path' of active processes (defined as from the main form down to the current active control) gets to update its Action states. The GUI Actions then instantly reflect the states (Visible/Enabled etc) of whatever matching Action (by name) were found in that path. The GUI Actions have no knowlege of any processes; the process Actions have no knowledge of any GUI controls. Everything just happens like magic.
Cool
This works similar in that everything is command driven, and commands are posted to a command manager which then places them into the top queue and if requested runs them immediately or when it has to. Commands have their own queue so when a command is ran, its queue is placed at the top of the active command stack, so you don't have to worry about commands being nested into other commands (through events for example, which aren't coupled). So an event handler of a button calls the process manager to do something (e.g. get metadata, delete an element) and it then calls through a command the proper method and from then on it's up to the event handlers everywhere what will happen in whatever order. I don't use multi-threading, as marshalling events back to the foreground thread is a slow process and the user has to wait anyway.
(the command stuff is all abstracted away btw, so you can very easily create undo/redo aware code. This code is coming with v3 as a separate library (algorithmia). )
The SortExpression can determine the names of the Fields is it sorting on can't it? The PropertyDescriptor has a string Name available. If the propertyDescriptor.Name is not used in any of the Fields from the SortExpression, then the sorting order can't have changed. Thats where I was going with that.
The sort expression can't determine the names of the fields, though the collection and the view can. I've to look into the propertydescriptor importancy to get a clear picture, at the moment it's not clear to me what difference the propertydescriptor makes with respect to the ListChanged event.