Sunday, March 28, 2010

Custom Events

UPDATE: I've updated my examples below, and added to some of the text, to take into account tergiver's suggestion, in his comment below, that I should probably use the generic EventHandler delegate (introduced in .NET 2.0) rather than defining custom delegates (which dates back to .NET 1.x). In fact, I've totally taken out the custom delegate code, since I doubt many people are still using .NET 1.1 anymore. If anyone reading this who is not using .NET 2.0 and higher would like to know about the custom delegate declarations and usage, let me know in a comment.

I tend to write introductory topics in my blog. Not always, but typically. And not because I’m a newcomer to .NET (I’ve been using .NET since the beginning of 2002), but because all the complicated topics seem to be covered by everyone else and I think there is still a need to address simpler topics.

Today’s topic is no different. Even though writing custom events isn’t all that complicated, I still see a lot of questions on the Forums asking how to do it. So, let’s get to it.

Say you have a custom UserControl that you need to have raise an event when, for example, a user types "FOO" in a textbox that is on the Control.

Minimally, in your UserControl, you need the following things:

// First you must specify the event that you will be raising:

public event EventHandler MyFooBar;

// Then, when you need to fire the event in your UserControl, do this:
if (this.MyTextBox.Text == "FOO")
this.OnMyFooBar(new EventArgs());

// Lastly, this raises the MyFooBar event:
protected virtual void OnMyFooBar(EventArgs e)
{
if (MyFooBar != null)
MyFooBar(this, e);
}

Then, in your form, you just set up the usual delegates and EventHandlers:

this.oMyControl.MyFooBar += new System.EventHandler(this.oMyControl_MyFooBarHandler);

private void oMyControl_MyFooBarHandler(object sender, System.EventArgs e)
{
// whatever your form code needs to be, such as:
MessageBox.Show("FOO was specified!");
}

Or, the alternative way to do this since anonymous methods were introduced in 2.0:

this.oMyControl.MyFooBar += delegate
{
// whatever your code needs to be, such as
MessageBox.Show("FOO was specified!");
};

You can even get more fancy, creating custom EventArgs and utilizing generic delegates, but the above code is sufficient for simple things, when just the built-in System.EventArgs is all you need. For fancier, custom stuff, try this:

First, custom event args something like this:

public class MyCustomEventArgs : EventArgs 
{
public bool IsBarSpecified { get; set; }
}

Your UserControl code then gets changed to this:

// Change the event so that it can be handled by a generic delegate

public event EventHandler<MyCustomEventArgs> MyFooBar;

// Firing the event gets changed to something like this:
if (this.MyTextBox.Text.ToUpper().Contains("FOO"))
{
MyCustomEventArgs e = new MyCustomEventArgs();
if (this.MyTextBox.Text.ToUpper().Contains("BAR"))
e.IsBarSpecified = true;
else
e.IsBarSpecified = false;
this.OnMyFooBar(e);
}

//And raising the MyFooBar event is the same, other than changing to the custom EventArgs:
protected virtual void OnMyFooBar(MyCustomEventArgs e)
{
if MyFooBar != null)
MyFooBar(this, e);
}

And in your Form, you'd have this instead:

this.oMyControl.MyFooBar += new EventHandler<MyCustomEventArgs>(this.oMyControl_MyFooBarHandler);

private void oMyControl_MyFooBarHandler(object sender, MyCustomEventArgs e)
{
// whatever your code needs to be, such as
string message = "FOO was specified!";

if (e.IsBarSpecified == true)
message += " BAR too!");

MessageBox.Show(message);
}

Or this, if you want to use an anonymous delegate (note that since I'm utilizing the custom MyCustomEventArgs, I'll need to specify them here, whereas I didn't need any of the parameters in the first example):

this.oMyControl.MyFooBar += delegate(object sender, MyCustomEventArgs e)
{
// whatever your code needs to be, such as
string message = "FOO was specified!";

if (e.IsBarSpecified == true)
message += " BAR too!");

MessageBox.Show(message);
};

Sunday, March 07, 2010

Uncommitted Child Table Changes

A few months ago, there was a question on the MSDN forums that reminded me of a similar question quite some time ago that I had answered. The issue crops up with parent/child DataTables using Relations. The symptoms of the problem are that your Child table is often left with uncommitted changes, even if you thought you properly did an .EndEdit() on that Child table. This blog post will explain why that happens and what to do about it.

OK, so let’s get some sample stuff set up first.

// First, let's set the BindingSource using MyRelation
this.bsParent = new BindingSource();
this.bsChild = new BindingSource();

this.bsParent.DataSource = this.MyDataSet;
this.bsParent.DataMember = "MyTable";
this.bsChild.DataSource = this.bsParent;
this.bsChild.DataMember = "MyRelation";

// now bind a grid and a textbox
this.oGrid.DataSource = this.bsParent;
this.txtLastName.DataBindings.Add("Text", this.bsChild, "description");


If you make a change in the TextBox (bound to the Child table), and move to other rows in the Grid (the Parent table), maybe even making other changes in the TextBox for each Grid row, then click on a Save button, when you do this.MyDataSet.GetChanges(), you will be missing some of those changes from your Child table. Here’s why:

1) Data in a DataRow has several different versions, one of which is a “Proposed” version and your changes are still stuck in this Proposed state and the .GetChanges() method only gets those with the “Current” version. See my earlier blog entry for a more in-depth explanation of DataRow versions and one way of handling this issue:  http://geek-goddess-bonnie.blogspot.com/2009/09/fun-with-datasets.html

2) Because you’ve made changes to child records that are related to different parent records, then the bsChild.EndEdit() only commits the Proposed changes for the current relation, even though you've changed other rows with different parents.

At first, I thought a good solution to this was to create a third BindingSource associated with the entire Child table, having nothing whatsoever to do with the Relationship. That way, when you Save, you could call bsChildTable.EndEdit() rather than bsChild.EndEdit(). Sounds good in theory, but unfortunately, it did *NOT* work.

So, are we stuck using the CommitProposedChanges() method I created in the above-mentioned earlier blog post? (You *did* read that post I hope).  Well, it will still work just fine. But, because we are using relationships and BindingSources based on those relationships, we can speed it up a bit as follows:

// defining ParentTable simply for clarity of the example
DataTable ParentTable = this.MyDataSet.Tables["MyTable"];

for (int i = 0; i < ParentTable.Rows.Count; i++)
{
this.bsParent.Position = i;
this.bsParent.EndEdit();
this.bsChild.EndEdit();
}


So, all we’re doing is spinning through the Parent table, moving the “record pointer” (setting the .Position property) to each parent row. This then “re-sets” the current relationship with each iteration through the Parent table’s rows so that the bsChild.EndEdit() applies to each successive relation.