Vibrant discussion about CSLA .NET and using the framework to build great business applications.
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/
Jonny Bekkum, Norway CslaContrib Coordinator
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.
Cymro: 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)); }
Tiago Freitas Leal, CslaGenFork (Open Source CSLA code generator)
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); }
// 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?