Hello all:
I've read various forums here on issues that occur when databinding RootLists to DataGridViews. I seem to be having pretty much the same issues described in those posts but, so far, none of the fixes seem to be working for me.
I started out with version 3.0.4 of the framework I upgraded to 3.5.1 this morning. I get different errors with the different versions.
With both versions I get EditLevel mismatch errors but in different places.
Scenario: TaxNotice_EditableRootList bound to a datagridview.
Each TaxNotice_EditableChild contains a TaxNoticeAction_EditableRootList which is bound to a second grid.
I have turned off AllowUserToAddRows and AllowUserToDeleteRows for both grids ( I perform adds and deletes through context menus).
There is a listbox that causes the initial datagrid (TaxNotices) to load for the selected Employer. The TaxNotices grid causes the TaxNoticeActions grid to display it's data (through the SelectionChanged event).
On SelectedIndexChanged of the listbox I do this
RebindUI(
false, false);_selectedEmployerID =
int.Parse(payrollEmployer_RORLListBox.SelectedValue.ToString());selectedPayrollEmployerTaxNotice_ERL =
PayrollEmployerTaxNotice_ERL.GetPayrollEmployerTaxNotice_ERL(_selectedEmployerID);BindUI();
My BindUI and RebindUI functions are based on the PTracker project except I've substituted my own objects.
After the above code has executed my EditLevels look like this
selectedTaxNotice_EditableRootList ---- 1
selectedTaxNotice_EditableRootList[0] ---- 2
selectedTaxNotice_EditableRootList[0].TaxNoticeAction_EditableRootList ----- 1
selectedTaxNotice_EditableRootList[0].TaxNoticeAction_EditableRootList[0] ----- 2
Previously with the older version I was not able to add new items to selectedTaxNotice_EditableRootList but other edits and cancels to existing items worked. However, with the latest version of CSLA I can add and then cancel new TaxNotice objects however, if I Add then edit the new item and then click cancel or save I get an EditLevel mismatch in CopyState error.
I will post the relevant parts of my code below. The business objects were all generated with CodeSmith. Sorry about the formatting if this doesn't turn out nicely. And TIA for reading!
Will
protected
void FormMain_Load(object sender, EventArgs e){
taxSpecialist_NVLBindingSource.DataSource = Hps.Business.
TaxSpecialist_NVL.GetTaxSpecialist_NVL();payrollEmployerTaxNoticeActionType_NVLBindingSource.DataSource = _payrollEmployerTaxNoticeActionType_NVL;
payrollEmployerTaxType_NVLBindingSource.DataSource = _payrollEmployerTaxType_NVL;
}
///This is what causes the main grid to refresh
private
void payrollEmployer_RORLListBox_SelectedIndexChanged(object sender, EventArgs e){
RebindUI(
false, false);_selectedEmployerID =
int.Parse(payrollEmployer_RORLListBox.SelectedValue.ToString());selectedPayrollEmployerTaxNotice_ERL =
PayrollEmployerTaxNotice_ERL.GetPayrollEmployerTaxNotice_ERL(_selectedEmployerID);BindUI();
}
private void btnSave_Click(object sender, EventArgs e){RebindUI(
true, true);} private void btnCancel_Click(object sender, EventArgs e){RebindUI(
false, true);}protected
void AddNewTaxNotice(object sender, EventArgs e){
if (selectedPayrollEmployerTaxNotice_ERL != null){
if (!selectedPayrollEmployerTaxNotice_ERL.IsDirty){
selectedPayrollEmployerTaxNotice_EC = selectedPayrollEmployerTaxNotice_ERL.AddNew();
selectedPayrollEmployerTaxNotice_EC.EmployerOID = _selectedEmployerID;
PayrollEmployerTaxNoticeAction_EC newPayrollEmployerTaxNoticeAction_EC = selectedPayrollEmployerTaxNotice_EC.PayrollEmployerTaxNoticeAction_ERL.AddNew();newPayrollEmployerTaxNoticeAction_EC.Description =
"Tax Notice Opened";newPayrollEmployerTaxNoticeAction_EC.PayrollEmployerTaxNoticeActionTypeID = _payrollEmployerTaxNoticeActionType_NVL.GetItemByValue(
"Opened").Key;payrollEmployerTaxNotice_ERLBindingSource.ResetBindings(
false);}
else{
MessageBox.Show("You currently have pending changes in an existing tax notice. Please save or cancel those changes before adding a new tax notice", "Unsaved Changes", MessageBoxButtons.OK);}}}
private
void BindUI(){
selectedPayrollEmployerTaxNotice_ERL.BeginEdit();
payrollEmployerTaxNotice_ERLBindingSource.DataSource = selectedPayrollEmployerTaxNotice_ERL;
}
private void RebindUI(bool saveObject, bool rebind){
// disable events this.payrollEmployerTaxNoticeAction_ERLBindingSource.RaiseListChangedEvents = false; this.payrollEmployerTaxNotice_ERLBindingSource.RaiseListChangedEvents = false; try{
// unbind the UIUnbindBindingSource(
this.payrollEmployerTaxNoticeAction_ERLBindingSource, saveObject, false);UnbindBindingSource(
this.payrollEmployerTaxNotice_ERLBindingSource, saveObject, true); //this.AssignmentsBindingSource.DataSource = this.ResourceBindingSource; // save or cancel changes if (saveObject){
if (selectedPayrollEmployerTaxNotice_ERL != null){
selectedPayrollEmployerTaxNotice_ERL.ApplyEdit();
try{
selectedPayrollEmployerTaxNotice_ERL = selectedPayrollEmployerTaxNotice_ERL.Save();
}
catch (Csla.DataPortalException ex){
MessageBox.Show(ex.BusinessException.ToString(), "Error saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);}
catch (Exception ex){
MessageBox.Show(ex.ToString(), "Error Saving", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);}}}
else{
if (selectedPayrollEmployerTaxNotice_ERL != null){
selectedPayrollEmployerTaxNotice_ERL.CancelEdit();
}}}
finally{
// rebind UI if requested if (rebind)BindUI();
// restore events this.payrollEmployerTaxNotice_ERLBindingSource.RaiseListChangedEvents = true; this.payrollEmployerTaxNoticeAction_ERLBindingSource.RaiseListChangedEvents = true; if (rebind){
// refresh the UI if rebinding this.payrollEmployerTaxNotice_ERLBindingSource.ResetBindings(false); this.payrollEmployerTaxNoticeAction_ERLBindingSource.ResetBindings(false);}}}
protected void UnbindBindingSource(BindingSource source, bool apply, bool isRoot){
System.ComponentModel.
IEditableObject current = source.Current as System.ComponentModel.IEditableObject; if (isRoot){
source.DataSource =
null;}
if (current != null){
if (apply){current.EndEdit();}
else{current.CancelEdit();}
}}
Anyone? This is really driving me up the wall. Not to mention oncoming deadlines.
For those not wanting to read the entire post below the gist is this. I am getting EditLevel mismatches when databinding to DataGridViews using the latest (and previous) version of CSLA and business objects generated with CodeSmith.
Depending on which version of CSLA I use I get different errors. I've followed everything that's been posted on this forum so far, unless I'm missing something.
Any input is much appreciated.
Thanks,
Will
The problem is probably something in your UI code. It is possible there yet another bug in CSLA in this regard, but I think we've finally found them all (and by "we" I mean people on this forum, like yourself, who've done a ton of research).
You should compare your UI code to this sample:
http://www.lhotka.net/cslacvs/viewvc.cgi/samples/trunk/RootChildGrandchildWinFormTest/
This is my core test for data binding. It works on 3.0.4 and 3.5.1 (and 3.6).
Hey Rocky. Thanks for the reply. I'm still having troubles. As far as I can tell, the only difference between our code is that you are using the property manager in your business objects and I am not. Could this be the cause of the problem? I am still getting EditLevel mismatch errors at different points.
For instance, if I click on my employer list (to cause the TaxNotice (root) and TaxNoticeAction (child) grids to load then immediately click cancel (without performing any edits) I get this error. I have stepped all the way down into the code and the error happes in line 258 of UndoableBase
((Core.IUndoableObject)value).UndoChanges(
this.EditLevel, BindingEdit); In this case "this.EditLevel" = 0 and "BindingEdit" = false.This call is made from the block of code inside"foreach (FieldInfo field in fields)" and the field in question is my TaxNoticeAction_EditableRootList (my child object -- actually collection of objects).
Any thoughts? I've pasted the entirety of my FormMain code below in case you want to load it into a code window and take a look. It's pretty simple stuff.
Thanks,
Will
using
System;using
System.Collections.Generic;using
System.ComponentModel;using
System.Data;using
System.Drawing;using
System.Text;using
System.Windows.Forms;using
Csla;using
Hps.Business;namespace
Hps.Windows.Applications.Enterprise.Modules.TaxNoticeManager{
public partial class FormMain : Form{
private int _selectedEmployerID; private FilteredBindingList<Hps.Business.PayrollEmployer_RORL> _payrollEmployer_FilteredBindingList; private PayrollEmployerTaxNotice_ERL selectedPayrollEmployerTaxNotice_ERL; private PayrollEmployerTaxNotice_EC selectedPayrollEmployerTaxNotice_EC; private ContextMenu _contextMenuForTaxNoticeDataGridView = new ContextMenu(); private PayrollEmployerTaxNoticeActionType_NVL _payrollEmployerTaxNoticeActionType_NVL = PayrollEmployerTaxNoticeActionType_NVL.GetPayrollEmployerTaxNoticeActionType_NVL(); private PayrollEmployerTaxType_NVL _payrollEmployerTaxType_NVL = PayrollEmployerTaxType_NVL.GetPayrollEmployerTaxType_NVL(); public FormMain(){
InitializeComponent();
}
protected void FormMain_Load(object sender, EventArgs e){
taxSpecialist_NVLBindingSource_ForGrid.DataSource = Hps.Business.
TaxSpecialist_NVL.GetTaxSpecialist_NVL();cboFilterTaxSpecialist.Items.Add(
new Csla.NameValueListBase<int, string>.NameValuePair(0, "Any")); foreach (Csla.NameValueListBase<int, string>.NameValuePair item in TaxSpecialist_NVL.GetTaxSpecialist_NVL()){
cboFilterTaxSpecialist.Items.Add(item);
}
payrollEmployerTaxNoticeActionType_NVLBindingSource.DataSource = _payrollEmployerTaxNoticeActionType_NVL;
payrollEmployerTaxType_NVLBindingSource.DataSource = _payrollEmployerTaxType_NVL;
}
protected void payrollEmployerTaxNotice_ERLBindingSource_CurrentChanged(object sender, EventArgs e){
payrollEmployerTaxNoticeAction_ERLBindingSource.EndEdit();
}
private void payrollEmployer_RORLListBox_SelectedIndexChanged(object sender, EventArgs e){
if (payrollEmployer_RORLListBox.SelectedValue != null){
_selectedEmployerID =
int.Parse(payrollEmployer_RORLListBox.SelectedValue.ToString());selectedPayrollEmployerTaxNotice_ERL =
PayrollEmployerTaxNotice_ERL.GetPayrollEmployerTaxNotice_ERL(_selectedEmployerID);BindUI(selectedPayrollEmployerTaxNotice_ERL);
}
}
protected void CloseTaxNotice(object sender, EventArgs e){
selectedPayrollEmployerTaxNotice_EC.CloseTaxNotice();
}
protected void ReOpenTaxNotice(object sender, EventArgs e){
selectedPayrollEmployerTaxNotice_EC.ReOpenTaxNotice();
}
protected void AddNewTaxNotice(object sender, EventArgs e){
if (selectedPayrollEmployerTaxNotice_ERL != null){
if (!selectedPayrollEmployerTaxNotice_ERL.IsDirty){
selectedPayrollEmployerTaxNotice_EC = selectedPayrollEmployerTaxNotice_ERL.AddNew();
selectedPayrollEmployerTaxNotice_EC.EmployerOID = _selectedEmployerID;
PayrollEmployerTaxNoticeAction_EC newPayrollEmployerTaxNoticeAction_EC = selectedPayrollEmployerTaxNotice_EC.PayrollEmployerTaxNoticeAction_ERL.AddNew();newPayrollEmployerTaxNoticeAction_EC.Description =
"Tax Notice Opened";newPayrollEmployerTaxNoticeAction_EC.PayrollEmployerTaxNoticeActionTypeID = _payrollEmployerTaxNoticeActionType_NVL.GetItemByValue(
"Opened").Key;payrollEmployerTaxNotice_ERLBindingSource.ResetBindings(
false);}
else{
MessageBox.Show("You currently have pending changes in an existing tax notice. Please save or cancel those changes before adding a new tax notice", "Unsaved Changes", MessageBoxButtons.OK);}
}
}
protected void AddNewAction(object sender, EventArgs e){
if (selectedPayrollEmployerTaxNotice_EC != null){
PayrollEmployerTaxNoticeAction_EC newPayrollEmployerTaxNoticeAction_EC = selectedPayrollEmployerTaxNotice_EC.PayrollEmployerTaxNoticeAction_ERL.AddNew();payrollEmployerTaxNoticeAction_ERLBindingSource.ResetBindings(
false);}
}
protected void payrollEmployerTaxNotice_ERLDataGridView_MouseUp(object sender, MouseEventArgs e){
// Load context menu on right mouse click DataGridView.HitTestInfo hitTestInfo; if (e.Button == MouseButtons.Right){
hitTestInfo = payrollEmployerTaxNotice_ERLDataGridView.HitTest(e.X, e.Y);
if (hitTestInfo.Type == DataGridViewHitTestType.ColumnHeader){
_contextMenuForTaxNoticeDataGridView.MenuItems.Clear();
_contextMenuForTaxNoticeDataGridView.MenuItems.Add(
"Add New Tax Notice", new EventHandler(AddNewTaxNotice));_contextMenuForTaxNoticeDataGridView.Show(payrollEmployerTaxNotice_ERLDataGridView,
new Point(e.X, e.Y));}
if (hitTestInfo.Type == DataGridViewHitTestType.Cell){
PayrollEmployerTaxNotice_EC notice = (PayrollEmployerTaxNotice_EC)payrollEmployerTaxNotice_ERLBindingSource.Current; if (notice.IsOpenTaxNotice){
_contextMenuForTaxNoticeDataGridView.MenuItems.Clear();
_contextMenuForTaxNoticeDataGridView.MenuItems.Add(
"Close Tax Notice", new EventHandler(CloseTaxNotice));_contextMenuForTaxNoticeDataGridView.Show(payrollEmployerTaxNotice_ERLDataGridView,
new Point(e.X, e.Y));}
else{
_contextMenuForTaxNoticeDataGridView.MenuItems.Clear();
_contextMenuForTaxNoticeDataGridView.MenuItems.Add(
"ReOpen Tax Notice", new EventHandler(ReOpenTaxNotice));_contextMenuForTaxNoticeDataGridView.Show(payrollEmployerTaxNotice_ERLDataGridView,
new Point(e.X, e.Y));}
}
}
else if (e.Button == MouseButtons.Left){
if (payrollEmployerTaxNotice_ERLDataGridView.CurrentRow != null){
if (payrollEmployerTaxNotice_ERLDataGridView.Columns[payrollEmployerTaxNotice_ERLDataGridView.CurrentCell.ColumnIndex].Name == "AddComment"){
commentTextBox.Left = e.X;
commentTextBox.Top = e.Y + 50;
commentTextBox.Text =
string.Empty;commentTextBox.Show();
commentTextBox.Focus();
}
}
}
}
protected void commentTextbox_LostFocus(object sender, EventArgs e){
if (commentTextBox.Text != string.Empty){
selectedPayrollEmployerTaxNotice_EC.AddComment(commentTextBox.Text, (
int)Security.CurrentUser.CurrentIdentity.User.EmployeeID.Value);commentTextBox.Text =
string.Empty;}
commentTextBox.Hide();
}
protected void payrollEmployerTaxNotice_ERLDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e){
}
protected void payrollEmployerTaxNotice_ERLDataGridView_SelectionChanged(object sender, EventArgs e){
try{
selectedPayrollEmployerTaxNotice_EC = (Hps.Business.
PayrollEmployerTaxNotice_EC)payrollEmployerTaxNotice_ERLDataGridView.CurrentRow.DataBoundItem; if (selectedPayrollEmployerTaxNotice_EC != null){
payrollEmployerTaxNoticeAction_ERLBindingSource.DataSource = selectedPayrollEmployerTaxNotice_EC.PayrollEmployerTaxNoticeAction_ERL;
payrollEmployerTaxNoticeAction_ERLBindingSource.ResetBindings(
false);payrollEmployerTaxNoticeAction_ERLDataGridView.Enabled =
true;}
else{
this.payrollEmployerTaxNoticeAction_ERLDataGridView.Enabled = false;}
}
catch (Exception ex){
}
}
protected void payrollEmployerTaxNoticeAction_ERLDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e){
}
protected void cboFilterTaxSpecialist_SelectedIndexChanged(object sender, EventArgs e){
ApplyEmployeeFilter();
}
protected void Search_KeyPress(object sender, KeyPressEventArgs e){
if (e.KeyChar == '\r'){
ApplyEmployeeFilter();
}
}
private void btnApplyEmployerFilter_Click(object sender, EventArgs e){
ApplyEmployeeFilter();
}
private void ApplyEmployeeFilter(){
if (cboFilterTaxSpecialist.SelectedIndex > 0){
payrollEmployer_RORLBindingSource.DataSource = Hps.Business.
PayrollEmployer_RORL.GetPayrollEmployer_RORL(txtFilterMerchantName.Text, txtFilterMerchantNumber.Text, ((Csla.NameValueListBase<int, string>.NameValuePair)cboFilterTaxSpecialist.SelectedItem).Key);}
else{
payrollEmployer_RORLBindingSource.DataSource = Hps.Business.
PayrollEmployer_RORL.GetPayrollEmployer_RORL(txtFilterMerchantName.Text, txtFilterMerchantNumber.Text, 0);}
}
private void btnSave_Click(object sender, EventArgs e){
this.payrollEmployerTaxNoticeAction_ERLBindingSource.RaiseListChangedEvents = false; this.payrollEmployerTaxNotice_ERLBindingSource.RaiseListChangedEvents = false; PayrollEmployerTaxNotice_ERL payrollEmployerTaxNotice_ERL = (PayrollEmployerTaxNotice_ERL)this.payrollEmployerTaxNotice_ERLBindingSource.DataSource;UnbindBindingSource(
this.payrollEmployerTaxNoticeAction_ERLBindingSource, true);UnbindBindingSource(
this.payrollEmployerTaxNotice_ERLBindingSource, true); this.payrollEmployerTaxNoticeAction_ERLBindingSource.EndEdit(); this.payrollEmployerTaxNotice_ERLBindingSource.EndEdit(); if (payrollEmployerTaxNotice_ERL != null){
payrollEmployerTaxNotice_ERL.ApplyEdit();
BindUI(payrollEmployerTaxNotice_ERL);
}
}
private void btnCancel_Click(object sender, EventArgs e){
this.payrollEmployerTaxNoticeAction_ERLBindingSource.EndEdit(); this.payrollEmployerTaxNoticeAction_ERLBindingSource.RaiseListChangedEvents = false; this.payrollEmployerTaxNotice_ERLBindingSource.RaiseListChangedEvents = false; PayrollEmployerTaxNotice_ERL payrollEmployerTaxNotice_ERL = (PayrollEmployerTaxNotice_ERL)this.payrollEmployerTaxNotice_ERLBindingSource.DataSource;UnbindBindingSource(
this.payrollEmployerTaxNoticeAction_ERLBindingSource, false);UnbindBindingSource(
this.payrollEmployerTaxNotice_ERLBindingSource, false); if (payrollEmployerTaxNotice_ERL != null){
payrollEmployerTaxNotice_ERL.CancelEdit();
BindUI(payrollEmployerTaxNotice_ERL);
}
}
private void BindUI(PayrollEmployerTaxNotice_ERL payrollEmployerTaxNoticeAction_ERL){
payrollEmployerTaxNoticeAction_ERL.BeginEdit();
this.payrollEmployerTaxNotice_ERLBindingSource.DataSource = payrollEmployerTaxNoticeAction_ERL; this.payrollEmployerTaxNoticeAction_ERLBindingSource.RaiseListChangedEvents = true; this.payrollEmployerTaxNotice_ERLBindingSource.RaiseListChangedEvents = true; this.payrollEmployerTaxNoticeAction_ERLBindingSource.ResetBindings(false); this.payrollEmployerTaxNotice_ERLBindingSource.ResetBindings(false);}
protected void UnbindBindingSource(BindingSource source, bool apply){
System.ComponentModel.
IEditableObject current = source.Current as System.ComponentModel.IEditableObject; if (!(source.DataSource is BindingSource)){
source.DataSource =
null;}
if (current != null){
if (apply){
current.EndEdit();
}
else{
current.CancelEdit();
}
}
}
}
}
Ok, I've rewritten the application to be pretty much just like the sample you provided. What I've done is added a root object (PayrollEmployee as editable root).
PayrollEmployee has an EditableRootList called PayrollEmployeeTaxNotice_ERL
PayrollEmployeeTaxNotice has an EditableRootList called PayrollEmployeeTaxNoticeAction_ERL
I've wired up my grids the same way yours is using a binding source tied to PayrollEmployer, a binding source tied to the PayrollEmployerTaxNotice property of the PayrollEmployer binding source and a binding source tied to the PayrollEmployerTaxNoticeAction property of the PayrollEmployerTaxNotice binding source.
Outside of the names "root", "children" and "grandchildren" we have the same code on our form and I still get errors with EditLevel mismatch.
At this point I can only think it's because my business objects are generated using the C# 2.0 templates and not the C# 3.5 templates which manage all of the properties differently.
Wasn't this functionality all available in CSLA 2.0? And ideas if I'm doing something wrong?
Thanks,
Will
Well, the reason for using EditableRootLists instead of EditableChildLists is the templates provide gets for EditableChildLists by dataReader only. In reality, my parent object has an ID which I then use to get the list of child objects. I don't return then as separate datatable objects.
I can write the filter criteria as part of my editable child list objects but it's code I was trying to not write since it seems like an object with a property that happens to be an Editable Root List would still be treated as a child when it comes to managing the EditLevel.
Will
ERLB (dynamic lists) can never be child objects. They are a root
list of root objects, and are designed to fill a very specific niche: editing
in a Windows Forms (or WPF or Silverlight) grid control such that the user’s
changes are committed as the user exits each row. Use of ERLB outside this
scenario is not supported, and often just won’t work.
ERLB doesn’t participate in undo as a child, because it is
not designed to ever be a child.
If you want a list of editable child objects, you must use BusinessListBase
to create an editable list of those objects. That’s the only model that
works.
Rocky
From: wjcomeaux
[mailto:cslanet@lhotka.net]
Sent: Tuesday, August 26, 2008 1:59 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] EditLevel Problem with Databinding
Well, the reason for using EditableRootLists instead of EditableChildLists
is the templates provide gets for EditableChildLists by dataReader only. In
reality, my parent object has an ID which I then use to get the list of child
objects. I don't return then as separate datatable objects.
I can write the filter criteria as part of my editable child list objects
but it's code I was trying to not write since it seems like an object with a
property that happens to be an Editable Root List would still be treated as a
child when it comes to managing the EditLevel.
Will
Ok, thanks guys. I'm working on the changes now.
Will
Copyright (c) Marimer LLC