In my Windows Form Application i have a datagridview bound to an Editable Child List representing a list of tariffs. I have some custom filters implemented through comboboxes and based on the values i select i build a filtered list for the tariffs.
Dim mFiltered As New Csla.FilteredBindingList(Of ApTariff)(oApmailItemCharacteristic.ApTariffs)
mFiltered.FilterProvider = AddressOf FilterTariff
mFiltered.ApplyFilter("", oCriteria)
Dim mSortedTariff As New SortedBindingList(Of ApTariff)(mFiltered)
mSortedTariff.ApplySort("WeightFrom", ListSortDirection.Ascending)
ApTariffsBindingSource.DataSource = mSortedTariff
The save for the object graph is working ok when editing existing rows on the tariffs grid, but when i try to add new rows i got an error of type "Edit Level Mismatch"
Also if the datagridview is empty because of the selection from comboboxes and i try to add a new row and press the escape key i got the following error:
System.ArgumentOutOfRangeException was unhandled
Message=Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
Source=mscorlib
ParamName=index
StackTrace:
at System.ThrowHelper.ThrowArgumentOutOfRangeException()
at System.Collections.Generic.List`1.get_Item(Int32 index)
at Csla.FilteredBindingList`1.OriginalIndex(Int32 filteredIndex)
at Csla.FilteredBindingList`1.System.ComponentModel.ICancelAddNew.CancelNew(Int32 itemIndex)
at Csla.SortedBindingList`1.System.ComponentModel.ICancelAddNew.CancelNew(Int32 itemIndex)
at System.Windows.Forms.BindingSource.System.ComponentModel.ICancelAddNew.CancelNew(Int32 position)
at System.Windows.Forms.CurrencyManager.CancelCurrentEdit()
at System.Windows.Forms.DataGridView.DataGridViewDataConnection.CancelRowEdit(Boolean restoreRow, Boolean addNewFinished)
at System.Windows.Forms.DataGridView.CancelEditPrivate()
at System.Windows.Forms.DataGridView.CancelEdit(Boolean endEdit)
at System.Windows.Forms.DataGridView.ProcessEscapeKey(Keys keyData)
at System.Windows.Forms.DataGridView.ProcessDataGridViewKey(KeyEventArgs e)
at System.Windows.Forms.DataGridView.OnKeyDown(KeyEventArgs e)
at System.Windows.Forms.Control.ProcessKeyEventArgs(Message& m)
at System.Windows.Forms.DataGridView.ProcessKeyEventArgs(Message& m)
at System.Windows.Forms.Control.ProcessKeyMessage(Message& m)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.DataGridView.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.Run(ApplicationContext context)
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.OnRun()
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.DoApplicationModel()
at Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.Run(String[] commandLine)
at MHS.Desktop.My.MyApplication.Main(String[] Args) in 17d14f5c-a337-4978-8281-53493378c1071.vb:line 81
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
Which version of CSLA do you use?
In windows forms it is essential to bind/unbind properly before calling Save. This is the most common reason for EditLevelMismatch Exception.
See my post: http://jonnybekkum.wordpress.com/2009/10/20/forms-databinding-the-magic-sequence-of-binduiunbindui/
Hi Jonny,
I have tried with the latest version of CSLA 4.5, and the problem persists. I am using CSLAExtender to manage the bind/unbind of bindingsources. The save is working ok when i filter, edit data in the grid, filter, edit again and save in the end. The problem occurs only when i try to add any new rows in the filtered datagrid.
Also there is that problem when adding a new row in the empty datagrid, and directly pressing ok. I think may be these two errors are related, and probably i am missing something there.
I get this too...but I don't think it is an "Edit Level Mismatch" type exception. I believe that the issue is in the FilteredBindingList (and posibly the SortedBindingList) implementation of ICancelAddNew.CancelNew (which is called by the DataGridView binding).
For some reason the DataGridView calls CancelNew with an index of -1 when the list is empty. and this causes an issue in the FilteredBindingList.OriginalIndex function. It attempts to find the OriginalIndex based on an index of -1.
void ICancelAddNew.CancelNew(int itemIndex)
{
ICancelAddNew can = _list as ICancelAddNew;
if (can != null)
can.CancelNew(OriginalIndex(itemIndex));
else
_list.RemoveAt(OriginalIndex(itemIndex));
}
private int OriginalIndex(int filteredIndex)
{
if (_filtered)
return _filterIndex[filteredIndex].BaseIndex;
else
return filteredIndex;
}
This can be replicated very easily by binding a FilteredBindingList to a DataGridView with DataGridView.AllowUserToAddRows = True. Apply a filter to the list which will cause the grid to be empty, click in the new row and then press escape. You should get a System.ArgumentOutOfRangeException.
I just checked the implementation of CancelNew in the SortedBindingList and this is Ok...I believe the missing line of code needs adding to the FilteredBindingList implementation...
void ICancelAddNew.CancelNew(int itemIndex)
{
//if (itemIndex > -1) return;
if (itemIndex <= -1) return;
ICancelAddNew can = _list as ICancelAddNew;
if (can != null)
can.CancelNew(OriginalIndex(itemIndex));
else
_list.RemoveAt(OriginalIndex(itemIndex));
}
Edit: I don't think the SortedBindingList implementation was right either! I've switched the condition and this seems to be Ok now.
Hi Cymro,
I feel a bit lost after your edit comment. The fix below applies to SortedBindingList.
Right?
The fixed line is missing on FilteredBindingList.
Right?
void ICancelAddNew.CancelNew(int itemIndex)
{
//if (itemIndex > -1) return;
if (itemIndex <= -1) return;
ICancelAddNew can = _list as ICancelAddNew;
if (can != null)
can.CancelNew(OriginalIndex(itemIndex));
else
_list.RemoveAt(OriginalIndex(itemIndex));
}
Hi Tiago,
Yes in our current version of CSLA - 4.0, the line was completely missing from FilteredBindingList and incorrect in the SortedBindingList. The implementation of this method should be the same in both classes.
I have tested this further and it appears that there are other issues around the CancelNew behaviour with a DataGridView. The reason I was looking at this was because we are attempting to use a SortedBindingList and FilteredBindingList with a DynamicBindingListBase derived class. The issue here is that when the list is Sorted (or filtered) the cancel new fails and leaves the invalid new item in the list.
I have tracked this down and found the issue but not a satisfactory resolution
When a new row is added to a DataGridView the AddNew adds the object to the end of the list. Even if the sorted is list is sorted it still goes at the end of the sorted list (which is what we want).
The issue is with the way that the DataGridView's DataBinding handles the cancelling.
We do not see the effect of this issue in a BusinessBindingListBase because as part of the CancelEdit on the child, it calls to the parent list to remove it (when the EditLevel < EditLevelAdded) using IParent.RemoveChild(T child). With a BusinessBindingListBase the implementation of this method removes the child, whereas with a DynamicBindingList does not. Therefore, with a BusinessBindingListBase the new child is removed from the list before the CancelNew is even called (as part of step 2 above) which masks the issue with the CancelNew implementation of the SortedBindingList.
I am unhappy with this "fix" for two reasons. Firstly, the original comment in the method...
...suggests that this was thought abount and decided this should not be done here. And secondly because this just masks the real issue of the list getting re-sorted during the whole cancel process.
Now the obvious "fix" is to have the DynamicBindingList remove the child as part of the IParent.RemoveChild implementation as follows...
/// <summary> /// This method is called by a child object when it /// wants to be removed from the collection. /// </summary> /// <param name="child">The child object to remove.</param> void Core.IEditableCollection.RemoveChild(Csla.Core.IEditableBusinessObject child) {
Remove((C)child); }
I am unhappy with this "fix" for two reasons. Firstly, the original comment in the method...
// do nothing, removal of a child is handled by
// the RemoveItem override
...suggests that this was thought abount and decided this should not be done here. And secondly because this just masks the real issue of the list getting re-sorted during the whole cancel process. Does anyone have any thoughts?
Hy Cymro,
Two comments:
1) On a dynamic list we can't just delete the child as one must also delete it from the database. So I can understand IParent.RemoveChild isn't deleting the child.
2) It looks like this bad behavior (aka bug) has been there forever. It works all right provided you dont wrap it in a SortedBindingList. I guess it wasn't tested on this scenario.
How about use the IParent.RemoveChild to remove the object, only when it's new?
void Csla.Core.IParent.RemoveChild(Core.IEditableBusinessObject child) { if (child.IsNew) Remove((T)child); }
Did you try it? Any side effects when it's not wrapped in a SortedBindingList?
Hi Tiago,
Thank you very much for taking the time to look at this.
Yes I can see how it was missed - the reason the SortedBindingList (and FilteredBindingList) get messed up is because the indexes get mixed up before databinding gets chance to call the ICancelAddNew.CancelNew method (which would otherwise sucessfully remove the item).
This really is a workaround fix, whereby the CancelEdit of the child object would result in the child being removed from the list (before it even gets to the ICancelAddNew.CancelNew on the list).
I guess the additional check does add a little more protection as it does check that the item has not yet been written to the Db (and thus does not need deleting). From an internal Csla point of view it is not really required, as the child will only call the IParent.RemoveChild if it IsNew and _uncommitted (this is the only place that I can see it is called from within Csla).
void System.ComponentModel.IEditableObject.CancelEdit() { if (!_disableIEditableObject && BindingEdit) { CancelEdit(); BindingEdit = false; if (IsNew && _neverCommitted && EditLevel <= EditLevelAdded) { // we're new and no EndEdit or ApplyEdit has ever been // called on us, and now we've been cancelled back to // where we were added so we should have ourselves // removed from the parent collection if (Parent != null) Parent.RemoveChild(this); } } }
If we were to implement this fix how can we get Rocky to look at it to fix it in the latest version of Csla?
I guess the additional check does add a little more protection as it does check that the item has not yet been written to the Db (and thus does not need deleting). From an internal Csla point of view it is not really required, as the child will only call the IParent.RemoveChild if it IsNew and _uncommitted (this is the only place that I can see it is called from within Csla).
(...)
If we were to implement this fix how can we get Rocky to look at it to fix it in the latest version of Csla?
Hi Cymro,
I'm finishing a test project to test for this fix. If the fix is ok, I guess Rocky won't object to include it in 4.5.40.
Hi Cymro,
I tested your suggested fixes and it works all right. I sent you a personal message you should find on your Conversations/Friends area on this forum.
Thank you Tiago,
Much appreciated.
Copyright (c) Marimer LLC