Adding New Record to DataView -- Why So Difficult?

  • Thread starter Thread starter Mark Olbert
  • Start date Start date
M

Mark Olbert

I am trying to do something that ought to be very simple. But I cannot get it to work.

What I want to do, on a data form, is allow the user to click a button to create a new record, which
is then initialized prior to any further editing. The data form's controls are all bound to one
particular dataview.

The strategy I'm using is:

1) create record

2) modify record in code (which changes the sort order location of where the new record should be in
the dataview)

3) find location of new record in dataview

4) reposition on new record

Oddly enough, I either can't find or can't reposition on the new record. I've tried creating the
record three different ways, by calling ...New() on the dataview's underlying datatable, by calling
AddNew() on the dataview and by calling AddNew() on the bindingcontext for the form.

In all cases, the new record is created and modified properly. But if I create the record in the
dataview or through the bindingcontext, Find() on the dataview won't find it. If I create the record
in the datatable the Find() on the dataview works, but I can't reposition because the assignement to
BindingContext.Position does...nothing. No error, no exception, but no change to the position value,
either.

This is very bizarre. How do others create new records on forms? Do you just leave them blank to be
filled in completely by the user? That won't work in my case.

- Mark
 
Mark:

I just wrote regarding your other post. I verified that if my
BindingContext has say 151 records with a current position at 150 and I try
changing the position to 155 for instance, 'nothing happens', the position
will stay at 150. No indexoutofrange exception.

Check somethign for me.... use the same code but don't fire the
find...using the BindingContext .AddNew() and verify that the record will
show up. Take out temporarily the extraneous code so that you can get
addnew to append the record.

Try using the DataTable.Select to find the row. I'm not sure why the sort
issue is necessary to have the view 'recognize' the new row.

Can you post the OnNewRecord implementation?

This is definitely weird, but I think we can figure it out.

Cheers,

Bill
Mark Olbert said:
I am trying to do something that ought to be very simple. But I cannot get it to work.

What I want to do, on a data form, is allow the user to click a button to create a new record, which
is then initialized prior to any further editing. The data form's controls are all bound to one
particular dataview.

The strategy I'm using is:

1) create record

2) modify record in code (which changes the sort order location of where the new record should be in
the dataview)

3) find location of new record in dataview

4) reposition on new record

Oddly enough, I either can't find or can't reposition on the new record. I've tried creating the
record three different ways, by calling ...New() on the dataview's
underlying datatable, by calling
 
Bill,

Here're the result of the experiment you asked me to run. The code is as follows (note that I've
stripped out my "initialize new record" routine to simplify the test environment, and the problem
still occurs):

this.BindingContext[formView].EndCurrentEdit();

this.BindingContext[formView].AddNew();

// formView -- the DataView -- shows, correctly, that a new record was added
// (i.e., formView.Count goes from 1807 to 1808)
// this next line is used to access the newly-added record to change it's sort field
int newPos = BindingContext[formView].Count - 1;

// sortfield is a field that doesn't exist in the underlying datatable (i.e., it's a "calculated"
// field added to the datatable after the datatable is filled from the server)
formView[newPos]["sortfield"] = "new sort key";

// retrieve the new value of the sort field to verify that we can, in fact, access the
// new record and that it "recorded" the assignment of the sortfield value
string toFind = (string) formView[newPos]["sortfield"];
this.BindingContext[formView].EndCurrentEdit();

// the new record is still in the DataView (formView.Count is still 1808)
// but the following formView.Find() returns -1, meaning the record wasn't
// found
int rowNum = formView.Find(toFind);
// what's bizarre is that if you "manually" check formView[1807]["sortfield"] in
// the Watch panel, it shows that the new record exists and that it's
// sortfield field has the value "new sort key". Somehow, the Find logic of the
// DataView isn't "checking" all the records

// this next line fails, as expected, because rowNum < 0... but rowNum
// shouldn't be less than 0, of course.
this.BindingContext[formView].Position = rowNum;

// interestingly, the newly-added row, which was in the DataView, is NOT in the
// underlying DataTable; the next line retrieves no rows
DataRow[] tblRows = ProjectData.DataSet.donation.Select("sortfield='" + toFind + "'");
int numTblRows = tblRows.Length;

I also tried forcing a resort on the DataView, to see if that would get it to "recognize" the
existence of the new record. I did this by inserting the following line after setting the value of
the sortfield for the new record:

formView.Sort = "sortfield";

This did not change anything. Interestingly, the newly added record didn't "move" in the row order
of the DataView, when it should have (i.e., the value I assigned wouldn't make it the last row, by
sort order, in the DataView).

I hope this helps identify the problem.

- Mark
 
I'm stumped...still looking around.

I found this on MSDN
Note This property was designed to be used by complex-bound controls,
such as the DataGrid control, to cancel edits. Unless you are creating a
control that requires this same functionality, it is not recommended that
you use this method. Instead, if the data source is either a DataView or
DataTable, use the DataRowView class's EndEdit method.

But there are more than a few examples doing exactly what you are doing
that works ok.

Somethign will turn up...
Mark Olbert said:
Bill,

Here're the result of the experiment you asked me to run. The code is as follows (note that I've
stripped out my "initialize new record" routine to simplify the test environment, and the problem
still occurs):

this.BindingContext[formView].EndCurrentEdit();

this.BindingContext[formView].AddNew();

// formView -- the DataView -- shows, correctly, that a new record was added
// (i.e., formView.Count goes from 1807 to 1808)
// this next line is used to access the newly-added record to change it's sort field
int newPos = BindingContext[formView].Count - 1;

// sortfield is a field that doesn't exist in the underlying datatable (i.e., it's a "calculated"
// field added to the datatable after the datatable is filled from the server)
formView[newPos]["sortfield"] = "new sort key";

// retrieve the new value of the sort field to verify that we can, in fact, access the
// new record and that it "recorded" the assignment of the sortfield value
string toFind = (string) formView[newPos]["sortfield"];
this.BindingContext[formView].EndCurrentEdit();

// the new record is still in the DataView (formView.Count is still 1808)
// but the following formView.Find() returns -1, meaning the record wasn't
// found
int rowNum = formView.Find(toFind);
// what's bizarre is that if you "manually" check formView[1807]["sortfield"] in
// the Watch panel, it shows that the new record exists and that it's
// sortfield field has the value "new sort key". Somehow, the Find logic of the
// DataView isn't "checking" all the records

// this next line fails, as expected, because rowNum < 0... but rowNum
// shouldn't be less than 0, of course.
this.BindingContext[formView].Position = rowNum;

// interestingly, the newly-added row, which was in the DataView, is NOT in the
// underlying DataTable; the next line retrieves no rows
DataRow[] tblRows = ProjectData.DataSet.donation.Select("sortfield='" + toFind + "'");
int numTblRows = tblRows.Length;

I also tried forcing a resort on the DataView, to see if that would get it to "recognize" the
existence of the new record. I did this by inserting the following line after setting the value of
the sortfield for the new record:

formView.Sort = "sortfield";

This did not change anything. Interestingly, the newly added record didn't "move" in the row order
of the DataView, when it should have (i.e., the value I assigned wouldn't make it the last row, by
sort order, in the DataView).

I hope this helps identify the problem.

- Mark
 
I did some spelunking after I accidentally discovered that eliminating all databinding for all the
fields on my form "solved" the problem (i.e., let me insert a record).

BTW, I'll note in passing that there was a tip-off that setting BindingContext.Position was going to
fail: when .NET is working properly, doing a BindingContext.AddNew() followed by changing the
sortfield value of the related DataView automagically changes BindingContext.Position to "follow"
the new, sorted position of the new record in the DataView. In retrospect, this is how one should
expect something like BindingContext to work.

Now for the interesting part. I eliminated DataBinding on all of the controls of my form and then
re-enabled it, one by one, to see which ones caused the problem. The following controls caused the
"why can't I add a new row?" problem:

all DateTimePickers
all CheckBoxes
all NumericUpDowns (however, there was only one instance on the form)
one (of 4) DropDown comboboxes

Leaving aside that oddball combobox, I then focused on the NumericUpDown. It's DataBinding setup is
as follows:

updnReps.DataBindings.Add("Value", formView, "reps");

Nothing odd there.

I next thought to try suspending and resuming databinding on the updnReps control before and after
adding a new record (i.e., maybe making the NumericUpDown control "ignore" binding while new records
were being added would solve the problem). Interestingly, the call to SuspendBinding() threw an
exception I've never seen before, complaining that a "...row that met all the binding constraints
could not be found..." [that's from memory].

So then I tried simply clearing the DataBindings collection for the updnReps control before adding
the new record, and then recreating the binding for it after calling EndEdit() on the new record.

That worked.

But it's really, really, ugly.

Oh, one other thing: I thought the problem might be (mostly) associated with trying to bind to the
"Value" property of the DateTimePickers and the NumericUpDown control (since that's a commonality
they share, in addition to not working :)). So I tried binding the "reps" database field to
DecimalPlaces property of the NumericUpDown control. That caused the problem, too, so it doesn't
look like the name of the property being bound to is the problem.

At this point I'm going to continue researching to see if I can figure out why that one (of 4)
combobox controls fails. I'm also going to put in a really ugly workaround so I can get back on my
project timeline.

But I would REALLY appreciate a response from Microsoft (or other knowledgeable parties) as to
whether this really is a bug in the DataBinding code and, if it is, is there a more elegant
workaround?

- Mark
 
Okay, some more info...

I believe the reason the DateTimePickers and the CheckBoxes mess up the DataBinding lies in how each
control deal (or, more accurately, fails to deal) with DBNull values.

I built a simple kludge that basically clears and later recreates all the "broken" databindings.
During the recreation phase, recreating the binding for the DateTimePickers threw an exception to
the effect that DBNull could not be cast to a value [this is from memory].

Now, it's perfectly reasonable that DBNull cannot be cast to a value; it's a null, after all. But
the real problem is that the DataBinding collection for, say, a DateTimePicker control doesn't throw
an exception when it tries to bind to a DBNull value. Instead, it fails silently, bringing down
(silently) the rest of the BindingContext logic.

I believe this also is the problem with the databinding for the checkboxes, because there I bet the
binding is failing on trying to convert a DBNull to either true or false. Personally, if I had to
choose, I'd map a DBNull to 'false' (and a DBNull to DateTime.MinDate), but an even better way would
be to throw an exception so that the programmer would be forced to decide what action to take.

Another approach would be to require that every field in a record be "required", because then the
AddRow() logic will fail if a field is not properly initialized. Personally, I don't think that's a
good route to go in practice; many database table designs have what amount to "optional" fields in
them (i.e., where the information isn't critical, but needs to be specified for some particular
row), and DBNull is a "valid" value for such fields to have.

I still don't know why that lone combobox isn't working, though :)

- Mark
 
Hello Mark...:-?

ahem...where's my soap box...ah here it is..

(steps up on soap box)

I've been doing VB development for over...ah what is it..13 -14 years now?
Anyway, since the first 'DataControl' and it's bound devils appeared in the
VB-2 extra tool kit (don't remember the name of the add-on) my experiences
have shown that using bound controls for in place editing have been, and
still are, a sure way to develop ulcers if what you are doing is more than a
trivial update. In my opinion using bound controls for data display only
and seperate edit forms is the best method to use. It's more code, but
infinitly less debugging. I have tools that autogenerate most of this code
anyway. Doing it this way gives you much more control over the editing
process including validation and edit state ( a whole 'nother subject ). I
genrally use a class object that represents a single row of data and
encapsulate all of the validation logic and pass that class object to my
updating routines. I never let an editing process directly touch a dataset
or datatable.

Ibrahim
ibrahimy@(removeantispantext)malluf.com




Mark Olbert said:
I am trying to do something that ought to be very simple. But I cannot get it to work.

What I want to do, on a data form, is allow the user to click a button to create a new record, which
is then initialized prior to any further editing. The data form's controls are all bound to one
particular dataview.

The strategy I'm using is:

1) create record

2) modify record in code (which changes the sort order location of where the new record should be in
the dataview)

3) find location of new record in dataview

4) reposition on new record

Oddly enough, I either can't find or can't reposition on the new record. I've tried creating the
record three different ways, by calling ...New() on the dataview's
underlying datatable, by calling
 
But what about those of us who don't have those code generators?? :)

You make a good point, and actually I tend to mix bound and unbound stuff on forms depending on how
"complicated" the validation requirements and/or interactions with other fields are. I would have to
own up to a bias towards using bound controls, when they "work", because the user experience, IMHO,
is "smoother" than with the kinds of separate display and edit routines I've come across.

- Mark
 
Okay, I've resolved this problem by creating Format and Parse event handlers for the DateTimePicker
and NumericUpDown controls' DataBinding context. To reiterate, the fundamental issue is that some of
the Windows.Forms controls do not handle DBNull values in a reasonable (IMHO) way; they mung up the
framework's BindingContext code without revealing that there's a problem.

The combobox problem was similar, but harder to solve, because while you can create Format and Parse
event handlers, they don't fire. For combobox controls I clear all the bindings before adding a
record, add the record, initialize it, and then rebind the controls.

Of course, this is not a "good" solution. Can you spell kludge? There's no reason why a
DateTimePicker control should mishandle a DBNull when a TextBox control doesn't. Sounds like a
significant architectural shortcoming to me!

In the hope that someone from Redmond sees this thread, I want to emphasize that, at the very least,
the framework ought to signal a problem somehow when a control can't handle a value being assigned
to it. This is particularly true when the resulting error condition brings down some other key logic
(i.e., BindingContext positioning) that, on the surface at least, appears to have absolutely nothing
to do with the source of the error (i.e., the ill-behaving control). It seems to me that objects
ought to be reasonably self-contained when it comes to errors, particularly those derived from value
assignment.

So please, with all the thousands of events and exceptions and whatnot in the .NET Framework...isn't
there room for a couple more to at least let us know when a data control has a problem? :)

- Mark
 
Back
Top