Customize StronglyTyped BindingSource Item Additio

2019-05-18 16:51发布

问题:

I want to customize the addition of a new item into a BindingSource (all strongly-typed) as described on the following MSDN Article:

How to: Customize Item Addition with the Windows Forms BindingSource

The code below results in InvalidOperationException: Objects added to a BindingSource's list must all be of the same type. Also, the object myTypesBindingSource.Current seems to be a DataRowView with my relevant row inside.

How can I customize the addition of a strongly-typed BindingSource?

private void InitializeComponent()
{
    this.components = new System.ComponentModel.Container();

    this.someDataSet = new myDB.SomeDataSet();
    this.myTypesBindingSource = new System.Windows.Forms.BindingSource(this.components);
    this.myTypesTableAdapter = new myDB.SomeDataSetTableAdapters.myTypesTableAdapter();
    this.tableAdapterManager = new myDB.SomeDataSetTableAdapters.TableAdapterManager();
    this.myTypesBindingNavigator = new System.Windows.Forms.BindingNavigator(this.components);

    this.someIntValueTextBox = new System.Windows.Forms.TextBox();

    // someDataSet
    this.someDataSet.DataSetName = "SomeDataSet";
    this.someDataSet.SchemaSerializationMode = System.Data.SchemaSerializationMode.IncludeSchema;

    // myTypesBindingSource

    // As generated:
    // this.myTypesBindingSource.DataMember = "myTypes";
    // this.myTypesBindingSource.DataSource = this.someDataSet;
    this.myTypesBindingSource.DataSource = this.someDataSet;
    this.myTypesBindingSource.AddingNew += new System.ComponentModel.AddingNewEventHandler(this.myTypesBindingSource_AddingNew);

    // myTypesTableAdapter
    this.myTypesTableAdapter.ClearBeforeFill = true;

    // tableAdapterManager
    this.tableAdapterManager.BackupDataSetBeforeUpdate = false;
    this.tableAdapterManager.myTypesTableAdapter = this.myTypesTableAdapter;
    this.tableAdapterManager.UpdateOrder = myDB.SomeDataSetTableAdapters.TableAdapterManager.UpdateOrderOption.InsertUpdateDelete;

    // someIntValueTextBox
    this.someIntValueTextBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.myTypesBindingSource, "someIntValue", true));
    this.someIntValueTextBox.Name = "someIntValueTextBox";
}

private void myTypesBindingSource_AddingNew(object sender, AddingNewEventArgs e)
{
    SomeDataSet.myTypesRow newRow = someDataSet.myTypes.NewmyTypesRow();
    newRow.someIntValue = 99;
    e.NewObject = newRow; 
}

回答1:

In the example, it is not a strongly typed BindingSource. As a matter of fact, the AddingNewEventArgs.NewObject property is an object. So, assigning it any derived type shall make it.

Also, notice that the example uses a class object DemoCustomer, it is no DataSet.Tables[0].Row which returns a DataRow. The game is a bit different, in my point of view, when using a DataSet.

When one uses a DataSet, you'll have to set only a DataTable as the BindingSource.DataSource, making you write something like:

BindingSource.DataSource = DataSet.Tables[0];

This way, when you add an item to the BindingSource.List using BindingSource.AddNew(), the BindingSource "knows" that it has a DataTable as its DataSource, so it calls the DataTable.NewRow() method and a new DataRow is added to your DataTable! Thus, having a DataRow to handle instead of a simple object.

Working with a DataRow

If you want to do similar to what the example on MSDN says, you'll have to create the row yourself.

DataRow newRow = DataSet.Tables[0].NewRow();
newRow.Columns["intColumn"] = 99;
e.NewObject = newRow;

This way, you shall be able to tell what default values you want.

Otherwise, if not, you might as well try this:

var newRow = (DataRow)e.NewObject;
newRow["intColumn"] = 99;

The weakness here is whenever you change the underlying database table column name, you'll have to come here, change the name of your intColumn, and recompile, and redeploy.

Besides, this shall not happen often, so it might be worthy depending on your environmental context.

EDIT #1

After having paid more attention to:

Also, the object myTypesBindingSource.Current seems to be a DataRowView with my relevant row inside

From MSDN: DataRowView

Whenever data is displayed, such as in a DataGrid control, only one version of each row can be displayed. The displayed row is a DataRowView.

A DataRowView can have one of four different version states: Default, Original, Current, and Proposed.

After invoking BeginEdit on a DataRow, any edited value becomes the Proposed value. Until either CancelEdit or EndEdit is invoked, the row has an Original and a Proposed version. If CancelEdit is invoked, the proposed version is discarded, and the value reverts to Original. If EndEdit is invoked, the DataRowView no longer has a Proposed version; instead, the proposed value becomes the current value. Default values are available only on rows that have columns with default values defined.

This means that when adding a new row, you're actually adding a DataRowView. You may access the current row by accessing its DataRowView.Row property.

Taking this into consideration, you might perhaps change the proposed solution in my initial answer to this:

var newRow = ((DataRowView)e.NewObject).Row;
newRow.Columns["intColumn"] = 99;

EDIT: (by Steven) Final Code

DataView dv = (DataView)myTypesBindingSource.List;
DataRowView drv = dv.AddNew();
SomeDataSet.myTypesRow newMyTypesRow = (SomeDataSet.myTypesRow)drv.Row;
newMyTypesRow.someIntValue = 53;
e.NewObject = drv;
myTypesBindingSource.MoveLast();