LLBLGen Pro v2.0 winforms binding refresh bug

Posts   
1  /  2
 
    
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 12-Sep-2006 16:00:32   

Hello, I was unpleasantly surprised when I encoutered this. It should be relatively simple to reproduce:

Put a DataGridView, BindingSource and a TextBox on a form. Set the datagrid's datasource to bindingSource, and bindigSource's datasource to the llblgen object. Add a binding of one of the object's properties to the textbox's text. When you start the app, whenever you move through the grid's rows, everything's fine, textbox is refreshed properly. When you change the value in the textbox and focus out, the behavior is strange, the grid is refreshed only the first time when you do this and never afterwards. So, if you have several textboxes, the grid won't be refresehed. It gets refreshed if it is repainted, for example, if you minimize and maximize the window, or if you simply give focus to the grid. If you go to debug and check the datasource in, for example, textbox's validated event, you'll see that the datasource is refreshed correctly, however the grid is also refreshed in debug mode, since it's always repainted when you switch between the app and the visual studio.

Mind that all of this works perfectly fine in LLBLGen Pro v1.0.2005.1, I tested it on the same example just to be sure.

*** EDITED ***

Sorry, I forgot to mention, the .NET environment is 2.0.


Regards,

Jaz

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 12-Sep-2006 16:19:27   

This is caused by IEditableObject. The thing is that when you select the row in the gridview, an editcycle is started. (IEditableObject.BeginEdit() is called by the grid). The textbox is bound to the same entity. so a change won't be propagated unless EndEdit is called(). This is done when you move away to another row in the grid.

Could you confirm that when you click row one in the grid, alter a few values in the textboxes, then move to row two of the grid, the grid's refreshed?

(I've seen this behavior as well, and to me it's related to .NET 2.0 specifically)

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 12-Sep-2006 17:10:46   

I confirm what you asked, but I don't understand. Are you saying that you can't do anything about this behavior, that it is caused by the .NET 2.0? I remind you again, everything works with LLBLGen Pro v1.0.2005.1 and .NET 2.0.

Thanks for the quick response.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 13-Sep-2006 10:24:31   

Please read my post below here: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7526#41558

which illustrates in short what's going on. I made a confusion between two things or at least it could be more confusing than necessary.

Jaz wrote:

I confirm what you asked, but I don't understand. Are you saying that you can't do anything about this behavior, that it is caused by the .NET 2.0? I remind you again, everything works with LLBLGen Pro v1.0.2005.1 and .NET 2.0.

Thanks for the quick response.

I've to see it for myself, but I have a hard time understanding that it did work in v1.0, because the code which causes this problem hasn't been changed between v1. and v2, but a small detail might make the difference, so I believe you when you say it works, I just have a hard time understanding why it does work in v1 as the code is the same (99% the same). The problem is this: when you select a row in a grid with entities, the grid calls BeginEdit on the entity (as the entity implements IEditableObject). This starts an edit cycle. When you then select a cell to edit (or start editing a cell), BeginEdit is called again. This is how it works, it's not how I would design it.

Now, this is done to allow you to press ESC to roll back values. Press ESC once to roll back the cell value, press ESC again to roll back all the changes of the row (entity).

When you bind a textbox to the collection, the entity bound to the textbox is the same as selected in the grid. This thus means that if you select a row in the grid, an edit cycle has started, even when you actually EDIT the entity through the textbox, that doesn't make a difference for the currency manager (of the form) which controls the whole binding and keeps everything in sync.

When a field is changed, and an edit cycle is in progress (as is the case) NO _entity _change event is raised. This is done to avoid triggering code which acts upon a change while the change isn't permanent (you can press ESC to roll back the change!). This thus also means that bound controls don't know if the entity has been changed UNLESS the change has been made final.

(Edit: I altered above paragraph. A property event IS raised, however the grid apparently doesn't act upon it).

A change is made final if EndEdit() is called for every BeginEdit that's been called. So when you move to another row, the grid calls EndEdit() on the entity it was 'editing' to make the changes final. This then makes the entity changed event raise and the control gets updated. The entity changed event triggers the view to update itself which causes a repaint of the row.

You can also trigger this mid-way, by calling EndEdit() on the entity edited when you leave a textbox (the only point in time when you can be SURE the value typed in is the one to set). Though that also means you won't roll back to a previous value if you press ESC.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 13-Sep-2006 11:26:50   

Otis wrote:

A change is made final if EndEdit() is called for every BeginEdit that's been called. So when you move to another row, the grid calls EndEdit() on the entity it was 'editing' to make the changes final. This then makes the events raise and the control gets updated.

Thats dangerous - IEditableObject allows for multiple calls to BeginEdit and implementers must store data on the first BeginEdit call and ignore the 2nd and subsequent calls to BeginEdit until a CancelEdit or EndEdit is called.

Heres my understanding: When a second BeginEdit call is received when a cell start to edit, this is just to ensure that BeginEdit has been called at least once and has nothing to do with indicating that a cell is being edited. (Presumably this is because the interface has no facility to determine whether an edit cycle has already begun and so the Grid (or whatever) has make sure)

ESC to rollback a change to a cell value is not a function of the IEditableObject interface but is performed by the grid and it should not be done by raising a CancelEdit (otherwise all changes to the entity/row will be rolled back).

There should be no counting to determine whether something is final or not

Whether an event should be fired when a value has changed (via binding) is debatable because I can see scenarios that would require the event during the edit cycle (eg. a user selects an item from a grid combo box and the event raised is used to modify the options available in another grid combobox). To me, an event should be raised and the event-implementer would have to check to see if the item is in an edit cycle or not if it needs to differentiate between in-edit-cycle or not. (maybe an InEditCycle readonly property reflecting _editCycleInProgress?)

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 13-Sep-2006 12:26:07   

Edit: Please read my post below here: http://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=7526#41558

to fully understand what's going on. I made confusing remarks here and there which could cause confusion

(contradictional text cleaned up)

simmotech wrote:

Otis wrote:

A change is made final if EndEdit() is called for every BeginEdit that's been called. So when you move to another row, the grid calls EndEdit() on the entity it was 'editing' to make the changes final. This then makes the events raise and the control gets updated.

Thats dangerous - IEditableObject allows for multiple calls to BeginEdit and implementers must store data on the first BeginEdit call and ignore the 2nd and subsequent calls to BeginEdit until a CancelEdit or EndEdit is called.

and what gives you the idea I didn't take care of that? simple_smile

Btw, every BeginEdit starts a new cycle. That some Microsoft controls call it at random isn't my problem (and they do, unfortunately). In general there are two Beginedit calls: one for the row and one for the cell. The entity doesn't have any more levels of editing so all subequential calls are ignored after the first one as it switches in editcycle mode at the first one. That the entity switches itself in editcycle mode on the first call is unavoidable. So it effectively handles the first call and ignores the rest.

Heres my understanding: When a second BeginEdit call is received when a cell start to edit, this is just to ensure that BeginEdit has been called at least once and has nothing to do with indicating that a cell is being edited. (Presumably this is because the interface has no facility to determine whether an edit cycle has already begun and so the Grid (or whatever) has make sure)

No, as the grid already has called BeginEdit, and there are two levels of rollback in a grid: cell and row.

The grid knows it called BeginEdit on the row. Therefore it also calls EndEdit for every beginedit on a row when focus moves to another row. Why should it call BeginEdit again on a cell if it KNOWS it has called BeginEdit already?

Btw the datagridview sometimes calls BeginEdit 3 times on a gridrow and sometimes 2 times.

ESC to rollback a change to a cell value is not a function of the IEditableObject interface but is performed by the grid and it should not be done by raising a CancelEdit (otherwise all changes to the entity/row will be rolled back).

This depends on what grid you're talking about, sadly enough. I've seen grids which made random calls, other grids didn't call at all etc. Very frustrating.

The .NET datagrid is also a mess in this. I've never been able to see a pattern in the call sequences of the grid, so the code in an entity contains safety code so nothing bad happens when a grid is going bezerk in this.

THe .NET 1.1 docs also didn't document it properly. The .NET 2.0 docs now say that you should remove the added element added by IBindingList.AddNew when CancelEdit is called. I can tell you, tracking that is messy.

There should be no counting to determine whether something is final or not

Whether an event should be fired when a value has changed (via binding) is debatable because I can see scenarios that would require the event during the edit cycle (eg. a user selects an item from a grid combo box and the event raised is used to modify the options available in another grid combobox).

It's not debatable. A changed event is final: the field has changed. An entity could be saved (auditing for example) because of this. Rolling back the values afterwards then makes it impossible to even use the changed event reliably.

It's even worse: when you add a new entity in a grid, and you press ESC twice, the entity is removed. I don't even want to know what kind of misery it will cause if all kinds of events are raised mid-way.

To me, an event should be raised and the event-implementer would have to check to see if the item is in an edit cycle or not if it needs to differentiate between in-edit-cycle or not. (maybe an InEditCycle readonly property reflecting _editCycleInProgress?)

No, that's non-OO. It's the entity's resposibility to signal if it's changed or not. In the middle of an editcycle it's not changed, as it all could be rolled back.

And I'm also not going to add a flag to make this possible, as it would otherwise become a true mess. An entity will tell you when a field is changed, the event is raised. Till then, the change isn't finalized and no event is thus raised. If you really want to, you of course can, by overriding OnFieldValueChanged in your entity and do whatever you want.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 13-Sep-2006 13:32:04   

Otis wrote:

(contradictional text cleaned up)

simmotech wrote:

Otis wrote:

A change is made final if EndEdit() is called for every BeginEdit that's been called. So when you move to another row, the grid calls EndEdit() on the entity it was 'editing' to make the changes final. This then makes the events raise and the control gets updated.

Thats dangerous - IEditableObject allows for multiple calls to BeginEdit and implementers must store data on the first BeginEdit call and ignore the 2nd and subsequent calls to BeginEdit until a CancelEdit or EndEdit is called.

and what gives you the idea I didn't take care of that? simple_smile

That you mention that a change is made final if EditEdit is called for every BeginEdit, therefore I assumed that you were not ignoring the number of BeginEdits called even if you don't resave the data.

Btw, every BeginEdit starts a new cycle. That some Microsoft controls call it at random isn't my problem (and they do, unfortunately). In general there are two Beginedit calls: one for the row and one for the cell. The entity doesn't have any more levels of editing so all subequential calls are ignored after the first one as it switches in editcycle mode at the first one. That the entity switches itself in editcycle mode on the first call is unavoidable. So it effectively handles the first call and ignores the rest.

This is where we disagree on a lot of points: - I am of the opinion that only the first BeginEdit starts a cycle. Subsequent BeginEdits don't (or shouldn't) start a cycle unless a CancelEdit or EndEdit has been called in the meantime. - Microsoft controls are not calling it at random but doing it because they have to as they have no knowledge over whether an edit cycle is in place or not. Even if they knew they had already started a cycle (eg your grid example), there is no guarantee that a cycle is still in place as a CancelEdit or EndEdit may have been called since. - You say here that subsequent BeginEdits are ignored but previously you said they were matched to EndEdits to decide whether a change is final or not: how are you matching them up?

Heres my understanding: When a second BeginEdit call is received when a cell start to edit, this is just to ensure that BeginEdit has been called at least once and has nothing to do with indicating that a cell is being edited. (Presumably this is because the interface has no facility to determine whether an edit cycle has already begun and so the Grid (or whatever) has make sure)

No, as the grid already has called BeginEdit, and there are two levels of rollback in a grid: cell and row.

The grid knows it called BeginEdit on the row. Therefore it also calls EndEdit for every beginedit on a row when focus moves to another row. Why should it call BeginEdit again on a cell if it KNOWS it has called BeginEdit already?

Btw the datagridview sometimes calls BeginEdit 3 times on a gridrow and sometimes 2 times.

The grid has already called BeginEdit but, as mentioned before, it has no knowledge of whether the edit cycle it thinks it started (BeginEdit may already have been called externally) therefore it must call BeginEdit again to make sure and it is allowed to do this any number of times by the interface contract. I don't believe that there is anything in the framework (binding or otherwise) to have two levels of rollback in a grid. This is purely down to the grid itself to implement: most if not all do this anyway but there is no requirement for them to do this and they could implement three or more levels if required; or take copies of all the property values and put them all back if ESC is pressed and ignore IEditableObject altogether.

ESC to rollback a change to a cell value is not a function of the IEditableObject interface but is performed by the grid and it should not be done by raising a CancelEdit (otherwise all changes to the entity/row will be rolled back).

This depends on what grid you're talking about, sadly enough. I've seen grids which made random calls, other grids didn't call at all etc. Very frustrating.

The .NET datagrid is also a mess in this. I've never been able to see a pattern in the call sequences of the grid, so the code in an entity contains safety code so nothing bad happens when a grid is going bezerk in this.

THe .NET 1.1 docs also didn't document it properly. The .NET 2.0 docs now say that you should remove the added element added by IBindingList.AddNew when CancelEdit is called. I can tell you, tracking that is messy.

There should be no counting to determine whether something is final or not

Whether an event should be fired when a value has changed (via binding) is debatable because I can see scenarios that would require the event during the edit cycle (eg. a user selects an item from a grid combo box and the event raised is used to modify the options available in another grid combobox).

It's not debatable. A changed event is final: the field has changed. An entity could be saved (auditing for example) because of this. Rolling back the values afterwards then makes it impossible to even use the changed event reliably.

It's even worse: when you add a new entity in a grid, and you press ESC twice, the entity is removed. I don't even want to know what kind of misery it will cause if all kinds of events are raised mid-way.

Which events exactly are we talking about here? I don't want to go off on one if we are talking at cross-purposes but If we are talking about the PropertyChange events (or INotifyPropertyChanged for .NET 2) then I have a problem with this. If you change a property via a property setter or via binding then that PropertyChanged event must be fired as the property has changed. If you don't then other controls data bound to the same property will not change to reflect the new value because the Binding is not receiving the event. (This seems to exactly the problem that Jaz had originally - however other situations may cause the binding to retrieve the current value such as a screen refresh which again Jaz mentions). If a property value changes then all controls bound to the same property should update themselves accordingly. This has nothing to do with IEditableObject at all and should be the same whether IEditableObject is implemented on the target object or not. CancelEdit should revert all of the changes and cause the PropertyChanged events (one for each property in .NET1 or a null or empty string parameter in .NET2) to fire again (which will update all bound controls). Adding an entity in a grid and pressing ESC twice removing the object is perfectly correct behaviour: this has aways been the case AFAIK although I agree the docs originally were crap.

To me, an event should be raised and the event-implementer would have to check to see if the item is in an edit cycle or not if it needs to differentiate between in-edit-cycle or not. (maybe an InEditCycle readonly property reflecting _editCycleInProgress?)

No, that's non-OO. It's the entity's resposibility to signal if it's changed or not. In the middle of an editcycle it's not changed, as it all could be rolled back.

And I'm also not going to add a flag to make this possible, as it would otherwise become a true mess. An entity will tell you when a field is changed, the event is raised. Till then, the change isn't finalized and no event is thus raised. If you really want to, you of course can, by overriding OnFieldValueChanged in your entity and do whatever you want.

I agree with the bit about the entity's responsibility to signal if it has changed or not but I disagree with you that it has not changed in an edit cycle. The fact that it could be rolled back is not relevant and neither is the method by which is does this. As well as the fields being reverted, so could the IsChanged flag.

IEditableObject is not required for two-way databinding but PropertyChanged events are. This is our fundamental disagreement and unless I'm proven to be wrong or I have misunderstood which events are being suspended, I believe that LLBLGen is breaking two-way data binding. confused

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 13-Sep-2006 14:19:06   

Ok, first things first: flushed I re-checked the code, I should have looked more closely. flushed What's done exactly is this: - when a field/property is set, the value is set. - when NO edit cycle is in progress the entity is signaled as being changed with the EntityContentsChanged event. Otherwise a flag is set so the event has to be raised when endedit is called. - no matter what, the PropertyChanged event is raised - when EndEdit is called, and a pending changed event is there, EntityContentsChanged is raised.

So the changed event for the field is raised no matter what.

The changed event for the ENTITY is raised after EndEdit. An EntityCollection (and thus an entityview) reacts on that and raises a ListChanged event. This thus means that in the case of the topicstarter, the events for the properties are raised, however the event for the entity isn't until EndEdit is raised.

simmotech wrote:

I agree with the bit about the entity's responsibility to signal if it has changed or not but I disagree with you that it has not changed in an edit cycle. The fact that it could be rolled back is not relevant and neither is the method by which is does this. As well as the fields being reverted, so could the IsChanged flag.

IEditableObject is not required for two-way databinding but PropertyChanged events are. This is our fundamental disagreement and unless I'm proven to be wrong or I have misunderstood which events are being suspended, I believe that LLBLGen is breaking two-way data binding. confused

Please show me where the rules are defined of 2-way databinding. IMHO there are NO rules for two way databinding other than: - use a datatable or - act like a datatable or - use trial/error and see if it works with all the controls out there.

But if you have found rules in the MSDN docs, please show me. I have been looking for them for years now. wink . One of the most frustrating parts of writing llblgen pro has been this, with databinding.

So how can llblgen pro break 2-way databinding when it does what it should do, signal when an entity field is changed? FYI: A datatable nor dataview supports PropertyChanged as an event. So how PropertyChanged is required for 2-way databinding isn't clear to me.

IEditableObject is required for grid-usage. You can't otherwise do proper value rollback or even row removal when a new row addition was canceled. So if you're binding to a grid, IEditableObject is more or less required.

So in short: property event is raised always, entity event (which is used by entityview / collection) is raised when endedit is called.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 13-Sep-2006 14:26:36   

To come back to the TS: the difference between 1.0.2005.1 code for .NET 2.0 and 2.0 code for .NET 2.0 is the PropertyChanged event. In the 1.0.2005.1 code, _fieldname_Changed events were raised, however in v2.0, the PropertyChanged event is raised. It can be the grid you're using isn't binding to that event. This is logical, as the datatable/dataview tandem doesn't expose this event.

At the end of the editcycle, with EndEdit(), EntityContentsChanged is raised, which triggers the view (indirectly) to raise an ListChanged event with the parameter that an entity at a given index has changed, which causes the bound controls to update themselves if they are displaying the entity in question.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 13-Sep-2006 15:57:39   

Otis wrote:

Ok, first things first: flushed I re-checked the code, I should have looked more closely. flushed What's done exactly is this: - when a field/property is set, the value is set. - when NO edit cycle is in progress the entity is signaled as being changed with the EntityContentsChanged event. Otherwise a flag is set so the event has to be raised when endedit is called. - no matter what, the PropertyChanged event is raised - when EndEdit is called, and a pending changed event is there, EntityContentsChanged is raised.

So the changed event for the field is raised no matter what.

The changed event for the ENTITY is raised after EndEdit. An EntityCollection (and thus an entityview) reacts on that and raises a ListChanged event. This thus means that in the case of the topicstarter, the events for the properties are raised, however the event for the entity isn't until EndEdit is raised.

Phew smile As long as the PropertyChanged event is raised (by whatever method) I am happy (and glad I put in a disclaimer in case we were talking about different events). The EntityContentsChanged event has no effect on databinding so its deferral isn't a problem.

Please show me where the rules are defined of 2-way databinding. IMHO there are NO rules for two way databinding other than: - use a datatable or - act like a datatable or - use trial/error and see if it works with all the controls out there.

But if you have found rules in the MSDN docs, please show me. I have been looking for them for years now. wink . One of the most frustrating parts of writing llblgen pro has been this, with databinding.

So how can llblgen pro break 2-way databinding when it does what it should do, signal when an entity field is changed? FYI: A datatable nor dataview supports PropertyChanged as an event. So how PropertyChanged is required for 2-way databinding isn't clear to me.

IEditableObject is required for grid-usage. You can't otherwise do proper value rollback or even row removal when a new row addition was canceled. So if you're binding to a grid, IEditableObject is more or less required.

So in short: property event is raised always, entity event (which is used by entityview / collection) is raised when endedit is called.

The rules for two-way binding are this: If the bound property has a mechanism for notifying that its value has changed then it is two-way binding otherwise the binding is considered one-way. (the one-way, two-way really refers to how automated the process is rather than whether it is possible or not).

One-way means that changes in a textbox will always be propagated back to the object property but direct changes via the property setter will not be noticed by the textbox until another event occurs which causes binding to reread the value (in this case minimizing and then maximizing the app but I seem to recall that whenever one-way binding is detected then any update causes EVERY binding to refresh - I had a conversation with an MS guy about how crap this was because a bug meant that this cascading causing hundreds of refreshed!)

The notification mechanism in .NET 1.x was having an event with a specific name (xxxChanged) which the binding infrastructure looks for and listens to (note binding infrastructure not the bound control or grid - they are more passive than you think!) In .NET 2.x either this method OR INotifyPropertyChanged can be used (the docs explicitly say one or the other but not both). The advantage of this is that only one event needs to be created (but that wasn't a problem anyway if the code was generated wink and it gives the advantage of being to specify null or an empty string as the property name in the event args to signal that all properties should be reread. Its minor disadvantage is that if you rename a property and forget to change the string to match when raising the event then the event is ignored)

PropertyDescriptors have an AddValueChanged method which the binding infrastructure uses to get an event notification when the property value changes. This means that Grids don't have to listen to the notification mechanism directly but they should use this method instead to abstract the exact mechanism used.

You don't actually bind to a DataTable directly but a DataView and DataView supports ITypedList and IBindingList. One of the members of IBindingList is SupportsChangeNotification (which always returns true) and ITypedList provides PropertyDescriptors which as we have seen is what is ultimately used during binding. DataView 'fakes' properties by providing DataColumnPropertyDescriptor instances based on the columns in the view.

I setup a test some time ago using a DevExpress grid, a textbox and a button (was checking out DevExpres XPO before I found LLBLGenPro). The grid and the textbox were bound to the same property on the same target object (which was a list). The button set a specific value on the property that the grid and textbox were bound to. If the bound property had an xxxChanged event then a change in the grid was propaged to the textbox (when the column editor was tabbed out of but not necessarily a different row) and changes in the textbox were automatically updated in the grid. Clicking on a different row in the grid meant that the textbox was updated to match. Pressing the button changed both grid and textbox even though it was not bound to anything. Comment out the event and it no longer worked as above: pressing the button did not show any chang until the textbox got focus at which time it 'realised' that the value had changed and displayed the new value. Same with the grid.

Hope that description helps.

Cheers Simon

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 13-Sep-2006 16:49:51   

simmotech wrote:

Please show me where the rules are defined of 2-way databinding. IMHO there are NO rules for two way databinding other than: - use a datatable or - act like a datatable or - use trial/error and see if it works with all the controls out there.

But if you have found rules in the MSDN docs, please show me. I have been looking for them for years now. wink . One of the most frustrating parts of writing llblgen pro has been this, with databinding.

So how can llblgen pro break 2-way databinding when it does what it should do, signal when an entity field is changed? FYI: A datatable nor dataview supports PropertyChanged as an event. So how PropertyChanged is required for 2-way databinding isn't clear to me.

IEditableObject is required for grid-usage. You can't otherwise do proper value rollback or even row removal when a new row addition was canceled. So if you're binding to a grid, IEditableObject is more or less required.

So in short: property event is raised always, entity event (which is used by entityview / collection) is raised when endedit is called.

The rules for two-way binding are this: If the bound property has a mechanism for notifying that its value has changed then it is two-way binding otherwise the binding is considered one-way. (the one-way, two-way really refers to how automated the process is rather than whether it is possible or not).

One-way means that changes in a textbox will always be propagated back to the object property but direct changes via the property setter will not be noticed by the textbox until another event occurs which causes binding to reread the value (in this case minimizing and then maximizing the app but I seem to recall that whenever one-way binding is detected then any update causes EVERY binding to refresh - I had a conversation with an MS guy about how crap this was because a bug meant that this cascading causing hundreds of refreshed!)

Ok, but are these rules set in stone? IMHO no they're not. There's no clear set of docs which state what to implement on either a control or a class to make this fully work. Sure you can dig through the interfaces docs (ITypedList, IBindingList and other nice interfaces) and you'll learn a lot, but it's very fragmented.

The notification mechanism in .NET 1.x was having an event with a specific name (xxxChanged) which the binding infrastructure looks for and listens to (note binding infrastructure not the bound control or grid - they are more passive than you think!) In .NET 2.x either this method OR INotifyPropertyChanged can be used (the docs explicitly say one or the other but not both). The advantage of this is that only one event needs to be created (but that wasn't a problem anyway if the code was generated wink and it gives the advantage of being to specify null or an empty string as the property name in the event args to signal that all properties should be reread. Its minor disadvantage is that if you rename a property and forget to change the string to match when raising the event then the event is ignored)

True, fully supported.

PropertyDescriptors have an AddValueChanged method which the binding infrastructure uses to get an event notification when the property value changes. This means that Grids don't have to listen to the notification mechanism directly but they should use this method instead to abstract the exact mechanism used.

You don't actually bind to a DataTable directly but a DataView and DataView supports ITypedList and IBindingList. One of the members of IBindingList is SupportsChangeNotification (which always returns true) and ITypedList provides PropertyDescriptors which as we have seen is what is ultimately used during binding. DataView 'fakes' properties by providing DataColumnPropertyDescriptor instances based on the columns in the view.

LLBLGen Pro does the same in v2. In v1, the EntityCollection classes implemented ITypedList (and IBindingList) and returned EntityPropertyDescriptor(2) instances. In v2, the entitycollection's implement IListSource, which delegate the binding to a different object, the entityview(2) object. (DataTable does this too). The entityview(2) classes implement ITypedList and IBindingList: they return EntityPropertyDescriptor(2) objects for the properties and control the IBindingList (and ITypedList) code/features.

The AddValueChanged method isn't important IMHO, the grid checks the events exposed (and in v2, it uses the notification interface) and binds to them.

I setup a test some time ago using a DevExpress grid, a textbox and a button (was checking out DevExpres XPO before I found LLBLGenPro). The grid and the textbox were bound to the same property on the same target object (which was a list). The button set a specific value on the property that the grid and textbox were bound to. If the bound property had an xxxChanged event then a change in the grid was propaged to the textbox (when the column editor was tabbed out of but not necessarily a different row) and changes in the textbox were automatically updated in the grid. Clicking on a different row in the grid meant that the textbox was updated to match. Pressing the button changed both grid and textbox even though it was not bound to anything. Comment out the event and it no longer worked as above: pressing the button did not show any chang until the textbox got focus at which time it 'realised' that the value had changed and displayed the new value. Same with the grid.

Though that's exactly what happens with llblgen pro code as well. The question here is though: when you alter the value in the textbox and tab away, to another textbox bound to another field of the same entity, why doesn't the CELL which contains the same value get updated, even though the event is apparently raised... ? I have no idea, but the propertychanged event IS raised.

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 14-Sep-2006 10:51:06   

Wow, I didn't know I'd cause such a fuss with my question.

Anyway, I read the whole discussion several times and as I understand:

  1. You changed the way that LLBLGen objects integrate with binding in v2.0 compared to the v1.

  2. This way may be better, you followed every rule from the MSDN docs and it should work, but it doesn't work with .NET 2.0 DataGridView for some reason.

  3. It may work with a third-party gridview component.

Well, I may agree or disagree with you on all your arguments, but simply - this is not good enough for me, and I guess it won't make a lot of other people happy. A lot of us is working for clients where they dictate what we can or cannot use in our projects. Beside that, I was perfectly happy how it was working in the v1 and I have to admit that I don't quite understand the benefits of this new binding infrastructure in the v2.0. I know that all the .NET versions had its glitches and we can blame it all on the Microsoft, but in the end we all have to live with it.

Having said all this, I do appreciate what you've done so far with LLBLGen, it is the best developer product ever IMHO. So, I'd like to kindly ask you for some workaround, either put the separate templates with the old binding infrastructure, or try to investigate further whether you can make this new binding infrastructure to work. Anything that may help, please...

Best Regards,

Jaz

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 14-Sep-2006 13:30:16   

I'm currently busy with a reply, I've spend the last 4 hours debugging and decompiling my own code and MS's, stay tuned.

(edit). Still no luck. I'm very mad and frustrated now as there's no proper docs, and the info is behind internal/private members rage ... However struggling on, there must be an answer somewhere why my own property descriptors don't get the eventhandler set and the raw ones DO, but after my own GetItemproperties routine...

(edit). Ok, after 6 hours straight fighting with databinding misery crap, I have light at the end of the tunnel. Stay tuned. (I'll post the full lengthy post about what I did soon for your entertainment simple_smile )

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 14-Sep-2006 17:28:25   

Ok, below my journey through databinding-crapcode-land. It took me more than 7 hours to get where I wanted to go but it payed of. I typed this during the session, so you can see what I tried. Read on.


I was typing a long post, but I thought... why not test this first? So I wrote a little test. Here's my testcode which opens the form:


[Test]
public void GridWithTextboxBindingUpdateTest()
{
    EntityCollection<CustomerEntity> customers = new EntityCollection<CustomerEntity>(new CustomerEntityFactory());
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        adapter.FetchEntityCollection(customers, null);
    }

    ResultsetViewer20 viewer = new ResultsetViewer20();
    viewer.BindCollectionAndTextbox(customers, "ContactName");
    viewer.ShowDialog();
}

ResultsetViewer20 is a form with a DataGridView and a couple of textboxes. BindCollectionAndTextbox does this:


public void BindCollectionAndTextbox(IEntityCollection2 toBind, string propertyToBind)
{
    _theGrid.DataSource = toBind;
    _boundTextBox.DataBindings.Add("Text", toBind, propertyToBind);
}

I run the test. When I move through the rows in the grid with the cursorkeys I see the ContactName reflected in the textbox.

So I choose a customer and click on the textbox. I alter the name and press TAB, to tab away from the textbox to another textbox (which isn't bound).

VOILA, the cell in the grid gets updated. I try this a couple of times. EVERY time this gets done.

Now I go to the grid and move to another row and back and click on the ContactName cell and update the name and move to the NEXT cell in the same row.

Nothing happens!

So I set a breakpoint in CustomerEntity.ContactName setter. Run in the debugger, and again, change the name in the grid, and go to the next cell in the row. Breakpoint gets hit, so I step into the code, value gets set etc. and I end up in PostFieldValueSetAction, the method which raises events after a succesful field set action. In there, it postponses the entitycontentschanged event (as an editcycle is going on), but it DOES call OnPropertyChanged. So I step into OnPropertyChanged.

In there, NO eventhandlers are bound to the event! So raising can't be done, as PropertyChanged is null, there are no eventhandlers bound.

No wonder the textbox doesn't get updated, the darn thing doesn't even listen to the proper events.

Ok, so lets try it with a BindingSource. My bindingmethod in the form changes a little:


public void BindCollectionAndTextbox(IEntityCollection2 toBind, string propertyToBind)
{
    BindingSource source = new BindingSource();
    source.DataSource = toBind;
    _theGrid.DataSource = source;
    _boundTextBox.DataBindings.Add("Text", source, propertyToBind, true);
}

I repeat what I did earlier: I select a row, go to the textbox, change the value and press TAB. The value gets changed. I repeat it.

NO update.

I have no idea, but as I proved earlier, the event is raised when something is listening, NO-ONE is listening though, so no event is raised, but what's worse: due to the usage of the bindingsource, the change isn't propagated to the grid.

The other way around still doesn't work, updating a cell doesn't make the textbox change unless the row is changed (== EndEdit is called).

I suspect the currencymanager doing the syncing or something, and I really don't understand why the grid NOR the textbox listens to the PropertyChanged event, as the entityview bound to both simply exposes entity objects which DO implement the interface.

So I did ANOTHER test, here I copy over the entities into a List<CustomerEntity> and bind that instead. I then bind the List<CustomerEntity> to the grid, and repeat my two tests: change the textbox, press TAB, repeat, also change the cell and go to next cell, repeat.

Both work. I then step into the code. Low and behold, someone listens to the PropertyChanged event!

The difference in code is that the entityview(2) create EntityPropertyDescriptor(2) objects, which are derived classes from PropertyDescriptor, and the List<> code doesn't do anything so the grid does a normal reflection cycle and uses internal property descriptors.

All set/get action by bound controls is going through these property descriptors. What could be the difference?

As Microsoft is so nice to provide NO documentation on this whatsoever (why would anyone write this code? You've datasets right? disappointed ), I decompiled the DataView's propertydescriptor, DataColumnPropertyDescriptor, and what do I see there:


public override void SetValue(object component, object value)
{
      ((DataRowView) component).SetColumnValue(this.column, value);
      this.OnValueChanged(component, EventArgs.Empty);
}

an event raise. Not something I do. So I add the event raise to the property descriptors. NO difference. Looking further, it seems this is also not the cause as this calls into the eventhandler set, but I don't have the eventhandler, I have the event.

So I tried something drastic: instead of my own propertydescriptors, I simply return the reflected ones directly. That makes it worse: no change to the textbox is propagated to the grid nor does the grid edit cell etc. work.

So there I was, no clue whatsoever what to do next. I tried my own BindingSource control class, where I overrided GetItemProperties. In there, when I used the List<CustomerEntity> I saw that the propertydescriptors did have an eventhandler set, which would invoke BindingSource.ListItem_PropertyChanged. So with the List<CustomerEntity> the BindingSource would bind to the event PropertyChanged, however with my own property descriptors, it wouldn't. Here it thus breaks somewhere.

The only difference between the two is that with the List<CustomerEntity> there are instances in the bound collection, which are reflected. With the EntityView(2) class, which implements ITypedList, there are no instances, it simply reflects over a type. This is required, as it otherwise won't know about properties when the collection is empty.

After a couple of hours I got really frustrated, but that's part of dealing with databinding code (I've spend at least 2 or 3 weeks trial/error on the complete databinding support code alone). So I decompiled the BindingSource class. I setup debug code with my own bindingsource derived class and set the grid's datasource first and then the datasource of the bindingsource object and set a breakpoint in the GetItemProperties override.

I then discovered that BindingSource does an Internal wiring. However, it checks for IRaiseItemChangedEvents. Checking the eventviewbase class, I saw I implemented that interface as well. I completely forgot this interface. It's new in .NET 2.0.

It's kind of ambiguistic: I do both: when a property changes I raise its own property changed event, but I also raise the entity's EntityContentsChanged event which ends up in the containing collection and view as a ListChanged event. This works OK, and it's more performant, as when you alter 10 fields in the same row of the grid, you won't get 10 event notifications and 10 repaints, you get just 1 after you're done.

However in THIS particular situation of this thread, it is inconvenient, because you want the field events to be propagated always, not after the row is done.

So I did a little test: return false from RaisesItemChangedEvents.

This was alittle more promissing: there was an eventhandler listening!. THe only thing that wasn't being done was that the control was repainted. So when I changed the cell's value and moved to the Next cell, the textbox wasn't updated until I put focus in the textbox. The opposite was also true: if I changed the textbox and pressed TAB, nothing happened unless I put focus in the cell which contained the same value. Something was indeed missing.

Checking the object which was bound to PropertyChanged, it was ReflectPropertyDescriptor... Huh.. confused

So I went back to returning the raw property descriptors (Which are of type ReflectPropertyDescriptor). Checking my bindingsource derived class, I saw that these property descriptors DID have the eventhandler set. There is however a problem: there's no way to obtain the list of eventhandlers from the raw property descriptor so I can add them to my own property descriptor! Well... there is, but it's called GetValueChangedHandler and it's protected internal, so I can't reach it without a dirty reflection hack which will likely fail in medium trust. Returning these property descriptors does make it all work normally however. disappointed

It's also at random. Sometimes the eventhandler is inside the propertydescriptor when the type is reflected, in other situations it's not (GetItemProperties is called 5 times or so before the collection with 91 entities is displayed... ).

I implemented aggregating the raw property descriptor so I could call it's SetValue() method and via that route invoke the event handler. However now the raw property descriptors also don't contain the event handler anymore. rage

However when I return my own property descriptors together with the raw property descriptors of the collections, I see that in the GetItemProperties override in my bindingsource class, the raw collection property descriptors DO have the eventhandler set!

Checking again in my own GetItemProperties in EntityView(2), I see that the second call to GetItemProperties() the raw property descriptors for the collections have the eventhandler set, but for the normal properties don't.

Are there any people left who still believe Microsoft databinding is properly designed and documented? If so, please tell me where to start looking then. disappointed

Ok, AGAIN, I try returning raw property descriptors. This works. I wonder why on earth this works. The SECOND time the GetItemProperties routine is called on EntityView(2), the raw properties contain the event handler, IF I've returned the FIRST time raw properties. If I return no raw properties the first time, the second time the raw properties DON'T contain the event handler.

Yeah, it's really my freaking fault this all doesn't work. disappointed . But I hate giving up after 5 hours straight of debugging, so I continue. So I change my code a bit with a dirty hack so that I return the raw property descriptors the first time, and all other times the normal property descriptors. This indeed gives the eventhandlers in the raw property descriptors the SECOND time the call comes. I have NO idea why.

So I run the test again. EVERYTHING works now.

However my dirty hack was a static counter which only works in the single test. The problem has to be somewhere with what I return as descriptors. So I check if the filtering on non-browsable properties makes any difference... No. So it's something with the property descriptor object I return (EntityPropertyDescriptor2) which apparently is different from the ReflectPropertyDescriptor.

Checking TypeDescriptor.GetProperties(type), I see it uses a completely different path as what BindingSource's call to TypeDescriptor.GetProperties(type, attributes) uses. Namely, it skips ICustomTypeDescriptor (which I don't implement, but still).

Before I could test it though, VS.NET 2005 found it enough and crashed on me. cry

Ok, restarted the session, rebuild and restarted the debug session. Passing an attribute array with Browsable(true) didnt make a difference.

So this was a dead end.

I then thought: something AFTER the first call to GetItemProperties adds the eventhandlers. So I did override AddValueChanged() in EntityPropertyDescriptor. And indeed, after the first call to GetItemProperties, BindingSource perfectly calls AddValueChanged.

This also solves the mistery: the second call to GetItemProperties (And all subsequential calls) create NEW property descriptors of type EntityPropertyDescriptor(2). But... the eventhandlers are already set on the FIRST ones. And as the event handlers are set on the EntityPropertyDescriptor instances I created, the cached ReflectPropertyDescriptor instances returned from TypeDescriptor.GetProperties() don't have them! THey only have them if I return them the first time, so the BindingSource sets the event handlers in the property descriptors, which are later on returned again when the property descriptors are requested.

Ok, now the cause is found for the missing event handler, I can remove the code to use the raw property descriptor as I have to cache the created EntityPropertyDescriptor(2) instances.

These are context specific, so I cache them per entityview(2) instance. What I have to add first is OnValueChanged() to Setvalue in the EntityPropertyDescriptor classes. Then I added a cache for the property descriptors for the types, per entityview(2) instance.

DONE!

It works. What a day, again databinding ruined my day, but what else is new. Ok, next test is of course: DESIGN TIME databinding.

It seems to stay up and running no matter what I do. So far so good simple_smile .

Bottom line: NO documentation about the fact that I have to CACHE the property descriptors otherwise it all will be dead in the water. Again, trial/erroring through this huge pile of databinding goo (my eyes really hurt now) eventually gives results, but it simply takes too long. And who on earth designs a piece of code which calls 5(!) times GetItemProperties and ASSUMES the propertydescriptors returned are cached and the same and THUS event handlers don't have to be set? Incredible.

Anyway, I managed to make it work. I've to see if it stays up in other code as well, but I have confidence it does.

I don't want to call this a fix for my code, as it does work as MS documented, however it simply required more code as MS forgot important parts of what's going on behind the scenes.

Below some answers to your questions/remarks.

Jaz wrote:

Anyway, I read the whole discussion several times and as I understand: 1. You changed the way that LLBLGen objects integrate with binding in v2.0 compared to the v1.

I implemented the NEW way of signaling to bound controls that properties were changed: Through a new event, PropertyChanged. This event IS exposed and the event IS raised (if anybody listens of course, otherwise you can't raise an event), and the interface which signals that the event is used IS implemented (INotifyPropertyChanged). What more can I do?

from the MSDN documentation of INotifyPropertyChanged:

For change notification to occur in a binding between a bound client and a data source, your bound type should either:

  • Implement the INotifyPropertyChanged interface (preferred).
  • Provide a change event for each property of the bound type.

Do not do both.

  1. This way may be better, you followed every rule from the MSDN docs and it should work, but it doesn't work with .NET 2.0 DataGridView for some reason.

Well, the DataGridView apparently doesn't listen to PropertyChanged for some reason. However, that's not my problem, but Microsoft's. the DataGridView is hardly without bugs, I can tell you that.

  1. It may work with a third-party gridview component.

Well, I may agree or disagree with you on all your arguments, but simply - this is not good enough for me, and I guess it won't make a lot of other people happy. A lot of us is working for clients where they dictate what we can or cannot use in our projects.

There's one thing I'll never do and that's taking the blame for a bug in Microsoft's software, OR for something YOU consider a bug which was never in the feature spec to begin with. I can only TRY to do the best I can, and use the material I have at hand, i.e. the MSDN docs and reflector.

Having said all this, I do appreciate what you've done so far with LLBLGen, it is the best developer product ever IMHO. So, I'd like to kindly ask you for some workaround, either put the separate templates with the old binding infrastructure, or try to investigate further whether you can make this new binding infrastructure to work. Anything that may help, please...

The required changes are available in the next build.

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 19-Sep-2006 11:43:35   

Thank you for your effort, I can imagine how you must have felt. Can I just ask you, I logged in to the customer section and the last 2.0 build available has a date September 8th 2006. Since your reply is written on September 14th, I'm not sure if this build includes these databinding changes or I need to wait for the next build. If it is the latter, then please tell me when this next build will be available.

Thanks again,

Jaz

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 19-Sep-2006 12:05:53   

Jaz wrote:

Thank you for your effort, I can imagine how you must have felt. Can I just ask you, I logged in to the customer section and the last 2.0 build available has a date September 8th 2006. Since your reply is written on September 14th, I'm not sure if this build includes these databinding changes or I need to wait for the next build. If it is the latter, then please tell me when this next build will be available. Jaz

They're not in that build yet. If you want to know which changes are in which builds, please consult the changelog browser in the customer area in the left menu.

The build with these changes is expected today (later today), as some other fixes have to be rolled into the build as well.

(it's available).

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 17-Nov-2006 12:26:12   

Remember me?

Hi again, you are not going to like this, but I'm having some serious problems with the binding and LLBLGen 2.0 again. This is the behavior:

Situation1: I change the property of an entity manually, for example, on button click I change the property, which is binded to a grid and to a textbox. Property is of type decimal (if it means anything to you). I went to debug, property indeed changes and the PropertyChanged event on an entity is fired, but both textbox and grid are not refreshed.

Situation 2: The same like 1, but the property is of type Guid and is binded to both grid and combobox. On some other controls Validated event I change this property to another value and in grid it is refreshed, but it is not refreshed in the dropdown. Again, property changed event happens fine.

In both cases EntityContentsChanged is not fired.

In 1.0.2005.1 version of LLBLGen, with FieldNameChanged events, everything works.

Now, I'm pretty certain this is Microsoft's mess again and I understand your frustration, but please understand mine as well. It's probably not likely that Microsoft will produce a fix for INotifyPropertyChanged any time soon, so what should I do now? Will you investigate this? Is there any chance that you provide some sort of backward compatibility option for FieldNameChanged events, so that they can still be generated if one wants so.

Thanks in advance.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 17-Nov-2006 12:37:18   

You're absolutely sure you're using the LATEST ormsupport dll build? could you post the buildnr of the ormsupportclasses dll you're using?

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 17-Nov-2006 12:56:20   

Yes, I downloaded the latest version couple of hours ago and checked all my references. To be sure, I went to the bin folder of my main app and checked the file version number of the SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll there. The version is 2.0.0.61107.

Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 17-Nov-2006 13:01:46   

If it may help you, in the situation 2 above, I have comboBox1 binded to Property1 and comboBox2 binded to Property2. When I change the value of comboBox1 by selecting a different value and tab out of the control, in the validated event I change the entity's Property2 based on Property1 value and the comboBox2 is not refreshed, but grid is. So, the property changed event is happening twice in this case. EntityContentsChanged event is not happening at all. That's the only thing I see different from situation1.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 17-Nov-2006 13:42:27   

Are these properties all Nullable<T> types? (So Nullable<Guid> and Nullable<Decimal>) ?

Also, as you can read in an earlier message in this thread: it already took me a looooooong time to get your previous issue reproduced and cleared up. Is this something DIFFERENT or are you suggesting this is the same thing? Because it did work as you can read above.

Also, how's the binding done? Could you please provide some code?

(I've enabled attachments for users on this forum, if you can't attach a file, please close your browser and re-connect. ) You can attach a file by clicking the paperclip with the '+' in the post header or when you're posting a new message, by checking the checkbox.

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 20-Nov-2006 10:17:07   

Hi, here's the sample project, I used the Northwind database, I made a backup on 2000 and restored it on 2005 SQL server, so I hoe you can do the same. I added 2 examples, one for nullable and the other for non-nullable field. They both behave the same way. If you change the textbox value everything is binded fine, if you press the button (hard-coded value changing from code), you can notice that nothing is refreshed, but the grid can be refreshed if you click on the cell representing the field whose binded value you've changed or if you select different row.

Regards.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 20-Nov-2006 10:40:46   

Thanks, I'll look into it.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39615
Joined: 17-Aug-2003
# Posted on: 20-Nov-2006 14:43:54   

This is the cause: when you bind a collection, all communication is focussed on IBindingList, and its ListChanged event. If you do this:


EntityCollection collection1 = new EntityCollection(new OrderDetailsEntityFactory());
using (DataAccessAdapter adapter1 = new DataAccessAdapter(Settings.Default.MainConnectionString))
{
    adapter1.FetchEntityCollection(collection1, null);
    IEntityView2 view = collection1.DefaultView;
    orderDetailsBindingSource.DataSource = view;
    view.ListChanged += new ListChangedEventHandler(view_ListChanged);
}

and in view_ListChanged you do:



void view_ListChanged(object sender, ListChangedEventArgs e)
{
    MessageBox.Show("ListChanged");         
}

You'll notice that ListChanged is received when you move to another row in the grid. This is because only then ListChanged can be raised, otherwise you'd get list changes for values which are by definition still temporary: you could roll back the value due to the IEditableObject calls to BeginEdit and EndEdit.

Clicking the cell will make the grid do a read, so it grabs the latest value, but that's in theory a temp value, unless you move to another row and EndEdit is called by the grid so the active entity is another entity and the values set are finalized.

If you add a single product entity to the form:

private ProductsEntity _product;

and fetch it:


_product = new ProductsEntity(2);
adapter1.FetchEntity(_product);

and add 2 textboxes, tbxRead and tbxWrite to the form (tbxRead is readonly) and a 3rd button and bind the stuff together like this:


tbxRead.DataBindings.Add("Text", _product, "UnitsOnOrder");
tbxWrite.DataBindings.Add("Text", _product, "UnitsOnOrder");

You'll see that when you click on that 3rd button and execute this code;

private void button3_Click(object sender, EventArgs e)
{
    _product.UnitsOnOrder = 100;
}

the textbox is updated immediately. This is because there's no edit cycle going on and the textbox doesn't have to deal with a collection with rollback of values due to IEditableObject.CancelEdit calls.

Of course, I also would have liked to see it differently, however there's not much I can do.

Frans Bouma | Lead developer LLBLGen Pro
Jaz
User
Posts: 31
Joined: 24-Feb-2006
# Posted on: 22-Nov-2006 16:31:36   

I didn't have time yesterday, but today I was reading your response and I finally managed to understand your explanation and what the whole problem is, I guess it's similar to this

http://www.windowsforms.net/FAQs/default.aspx?PageID=2&ItemID=717&CategoryID=63&tabindex=3

For me, it's stupid that if an object implements the IEditableObject, it is forced to go in the edit cycle and only when being in collection. I don't know what Microsoft people were thinking when they designed this, but practicaly they force a specific UI behavior, which may not be wanted by everyone and in every situation. The possibility of CancelEdit is great, but only if one wants so. There should be a property which would control whether I want the edit cycle or immediate change. I can understand that this is not your problem, but Microsoft's, because, I might want the edit cycle on one screen and immediate binding on another for the same entity class, so in one case, it would need to implement IEditableObject, in another case it shouldn't.

So, I guess, for LLBLGen's entities in collections which are used also in simple binding (textbox), we must produce some sort of a workaround like in the link above. I did that on a global level in my app, because I don't plan to have cancellation of changes anywhere.

Thanks for your response and sorry for all the fuss.

1  /  2