Monday, November 30, 2009

Hooking Into the Form's Events, from Form Controls

Have you ever had the need to have a Control on your Form hook into some of the Form’s events? For example, say you have a Control on your Form that needs to do something extra when the Form Loads or the Closing event is triggered.

Let's use an example to illustrate how we might accomplish this. Let's use the ListView as an example. First, we'll sub-class the ListView Control. As you can see, we override the OnParentChanged event in the ListView sub-class.

We’ll utilize the TopLevelControl (not the Parent). If the TopLevelControl is null then we hookup event handlers backwards through the control hierarchy as each control is parented. When we finally get to a TopLevelControl for a Form type we hookup the load/closing event handlers.

public class MyListView : System.Windows.Forms.ListView
{
private Form FormParent = null;

public MyListView()
{
}

protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);

if (this.FormParent != null)
return;

if (this.DesignMode == false)
{
if (this.TopLevelControl != null && this.TopLevelControl is Form)
{
this.FormParent = (Form)this.TopLevelControl;
this.EstablishParentEvents();
}
else
{
Control LastParent = this;

while(LastParent != null)
{
LastParent.ParentChanged += new EventHandler(LastParent_ParentChanged);
LastParent = LastParent.Parent;
}
}
}
}

private void EstablishParentEvents()
{
this.FormParent.Closing += new CancelEventHandler(MyListView_Closing);
this.FormParent.Load += new EventHandler(MyListView_Load);
}

private void MyListView_Closing(object sender, CancelEventArgs e)
{
// put your extra code here

if (this.FormParent != null)
{
this.FormParent.Closing -= new CancelEventHandler(MyListView_Closing);
this.FormParent.Load -= new EventHandler(MyListView_Load);
}
}

private void MyListView_Load(object sender, EventArgs e)
{
// put your extra code here
}

private void LastParent_ParentChanged(object sender, EventArgs e)
{
Control Source = (Control)sender;

if (Source.TopLevelControl != null && Source.TopLevelControl is Form)
{
this.FormParent = (Form)Source.TopLevelControl;
this.EstablishParentEvents();
}
}
}

Thanks to Neil Tonkin in Message #1084683 from the Universal Thread

Friday, November 20, 2009

Reflection Class Redux

I’m sorry to say that there was a bug of sorts in the MyReflectionClass posted in my blog entry back in September (Reflection In .NET). It was in the LoadAssembly() method and it’s been corrected in the original post.

One of the comments in that original post (the comment has been modified now) was that you might need to include the full path of the Assembly as part of the Assembly name. Well,  we really do have to take into account a path to the Assembly, but not as part of its name, which was the implication of my original comment. 

Sorry for any confusion or pulling of hair that this has caused. =0(

Thursday, November 19, 2009

Dynamic Menu Items

There was a question recently on the Universal Thread about dynamically adding MenuItems to a Form. The data for these dynamically added Items can be obtained from anywhere, like from your database. This fits in quite nicely with the first substantial post I wrote in this blog back in September, Reflection in .NET, because you can use these dynamically added Items to launch new DLLs that you might create (for example, a custom form for one of your customers), via Reflection, without having to recompile and re-distribute your entire application. Just supply the newly-created DLL. At my last company, we used DevExpress SideBars and incorporated this dynamic functionality that way.

So, without further ado, here’s the first thing: the code you need to dynamically add the menu items. Note that this creates different sub-classes of MenuItems, depending on your data:

private void TestMenuAddItemsDynamically()
{
MenuItem item; // This is so you can use MenuItem sub-classes
foreach (DataRow row in this.oData.Rows)
{
if (string.IsNullOrEmpty(row["assembly"].ToString()) == false)
{
item = new LaunchMenuItem(row["description"].ToString(), row["assembly"].ToString(), row["class"].ToString());
this.AddMenuItem("Custom Forms", item);
}
else
if (string.IsNullOrEmpty(row["url"].ToString()) == false)
{
item = new FavoritesMenuItem(row["description"].ToString(), row["url"].ToString());
this.AddMenuItem("Favorites", item);
}
else
{
item = new DefaultMenuItem(row["description"].ToString());
this.AddMenuItem("Dynamic Menu", item);
}
}
}

Next, I’ll show you the LaunchMenuItem class, since it is what this post is focusing on.

// Used to launch a DLL by Reflection
public class LaunchMenuItem : MenuItem
{
protected string AssemblyName;
protected string ClassName;

public LaunchMenuItem(string text, string assemblyName, string className)
: base(text)
{
this.Click += new System.EventHandler(this.ClickHandler);
this.AssemblyName = assemblyName;
this.ClassName = className;
}
protected virtual void ClickHandler(object sender, System.EventArgs e)
{
MyReflectionClass oReflect = new MyReflectionClass(this.AssemblyName, this.ClassName);

// In it's simplest usage, I will assume that the class I am instantiating is a Form
// Expanding on this concept is left as an exercise for the reader.
// I've also left out error checking for the instantiated object

string message = "";
Form oForm = oReflect.InstantiateClass(ref message) as Form;
if (oForm == null)
MessageBox.Show(message);
else
oForm.Show();
}
}

Note that the above class uses the MyReflectionClass from my previous post on the subject, Reflection In .NET.

The other custom MenuItem classes would contain other things to do in their Click event handlers, such as browsing to a URL link inside a BrowserControl on your Form (for the FavoritesMenuItem class). I’m not including that code in this example … I leave it as an exercise for the reader.

There’s one more method to show, and that’s a common method that will add any MenuItem to the Form’s Menu. Notice that it does things like check to see if the top menu item already exists (“Custom Forms”, “Favorites” and “Dynamic Menu” in the above example). It adds it if it doesn’t, and then adds the new MenuItems under it.

// Generic method for adding to any Menu
private void AddMenuItem(string name, MenuItem item)
{
string lookForName = name.Replace("&", "");

// Find top-level menu item
MenuItem addTo = null;
for (int i = 0; i < this.Menu.MenuItems.Count; i++)
{
MenuItem topLevel = this.Menu.MenuItems[i];
if (string.Compare(topLevel.Text.Replace("&", ""), lookForName, true) == 0)
{
addTo = topLevel;
break;
}
}

// Was a top-level menu item found?
if (addTo == null)
{
addTo = new MenuItem(name);
this.Menu.MenuItems.Add(addTo);
addTo.Index = this.Menu.MenuItems.Count - 2;
}

addTo.MenuItems.Add(item);
}

That about wraps it up. I hope this has given my readers lots of ideas to go forth and try out some cool dynamic stuff.

Sunday, November 15, 2009

Setting Focus to a Control at Form Load

I used to write a “.Net Tips and Tricks” column for an online magazine associated with a forum called the  Universal Thread. The magazine has been known by several names, the Universal Thread Magazine, Level Extreme .NET Magazine and simply the UT Mag. Unfortunately, after being in “publication” since 2001, the last issue was in April of this year and it doesn’t look like it will be published anymore.

So, if no one minds, I think it’s time to start including a few of the Tips from my columns over the years. The format of my column was to garner tidbits of wisdom from other people’s posts on the UT, re-work them into a good format for a Tip, and credit the people who wrote the post (sometime it was even from a post that I wrote).

So, let’s start out with this --- how about a simple WinForms tip. Nothing earth-shattering here, but worth mentioning.

Normally, the Focus() method of a control is used to move the focus to that control:

private void MyButton_Click(object sender, System.EventArgs e)
{
// Move the Focus elsewhere
this.MySpecialTextBox.Focus();
}

This works at any time, except during Form/Control Load. Then, you simply have to set the ActiveControl:

private void MyForm_Load(object sender, System.EventArgs e)
{
// Start the Form with the Focus on a certain control
this.ActiveControl = this.MySpecialTextBox;
}

Thanks to Kevin McNeish in Message #1080245 on the Universal Thread.

Thursday, November 05, 2009

Getting Non-Null Data

UPDATE: See my new post on this topic. (I also corrected code in this current post, where I changed to test for null before testing for DBNull.Value ... it was an oversight on my part. Sorry, hope it didn't cause anyone problems).

I see questions along these lines all the time:

//I'm doing this:

int CustID = (int)dsCustomer.Tables["Customer"].Rows[0]["CustID"];

// It throws an exception because CustID is DBNull in the data.
// How do I handle this?


I have a CommonFunctions class that I use for things such as a GetNonNull( ) method (it's an old class, dating back to the 1.1 days, but it still works fine and so I have never updated it for 2.0, haven't really looked to see if it's even necessary).  This will work for any type of object, not just data in a DataSet, but is seems that DataSet access is  the commonly asked question.

This is implemented with many overloads, but here's an example of just a few:

public class CommonFunctions
{
public static object GetNonNull(object Test, object Default)
{
if (Test != null && Test != DBNull.Value)
return Test;
else
return Default;
}
public static string GetNonNull(object Test, string Default)
{
if (Test != null && Test != DBNull.Value)
{
if(Test is DateTime)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);

if(TestDT == SqlNull)
return Default;
}
else
if (Test is bool)
{
bool YesNo = Convert.ToBoolean(Test);
if (YesNo)
return "Yes";
else
return "No";
}

return Test.ToString().Trim();
}
else
return Default;
}
public static int GetNonNull(object Test, int Default)
{
if (Test != null && Test != DBNull.Value)
return Convert.ToInt32(Test);
else
return Default;
}
public static DateTime GetNonNull(object Test, DateTime Default)
{

if (Test != null && Test != DBNull.Value)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);
DateTime NetNull = new DateTime(1, 1, 1);

if(TestDT != SqlNull && TestDT != NetNull)
return TestDT;
else
return Default;
}
else
return Default;
}
}

These are just a sample of the overloads I use. I have a few more such as for bool, decimal, long and even a few differently named methods specifically for Dates, such as these next two:

public static string GetNonNullDate(object Test, string Default)
{
if (Test != null && Test != DBNull.Value)
{
if(Test is DateTime)
{
DateTime TestDT = Convert.ToDateTime(Test);
DateTime SqlNull = new DateTime(1900, 1, 1);

if(TestDT != SqlNull)
return TestDT.ToShortDateString();
}

return Default;
}
else
return Default;
}
public static DateTime GetNonNullDate(object Test)
{
if (Test != null && Test != DBNull.Value && Test is DateTime)
return Convert.ToDateTime(Test);
else
return new DateTime(1900, 1, 1);
}


Anyway, you get the point. Note that these are static methods, so no instantiation of the CommonFunctions class is necessary.To use them, you would simply have something like this:

int MyInt = CommonFunctions.GetNonNull(MyDataSet.Tables[0].Rows[0]["MyColumn"], 0);

// -or-

DateTime MyDatetime = CommonFunctions.GetNonNullDate(MyDataSet.Tables[0].Rows[0]["MyDateColumn"])