July 2009 - Posts

Whether it is a brisk walk, a game of golf, killing aliens, or a good book, everyone needs a little down time to recharge. And several times a year, it is good to extend that downtime and let the mind relax and run free.

I am lucky enough to have a sister with a lake house (and every possible accoutrement) and a brother with a gentleman’s farm. So I get some serious recharging time each year to breath the fresh lake air, enjoy water sports, play lawn games, visit the horses, chickens, and pigs, eat great food fresh from the garden, and just hang out with enjoyable relatives.

Hope you find some time to recharge this summer!

with no comments
Filed under:

Coalesce Operator

The coalesce operator, also called the null coalescing operator, is new in VB 9 and C# 2.0. It helps you work with nulls (Nothing in VB).

The coalesce operator involves two expressions. The first expression must evaluate to a nullable type or reference type. The first expression is the result if the first expression evaluates to a non-null value. The second expression is the result if the first expression evaluates to null (Nothing in VB).

In C#:

string result = value ?? "<Empty>";

?? is the coalesce operator in C#. It is of the general form:

Exression ?? NullResult

In VB:

Dim result As String = If(value, "<Empty>")

The If keyword is used as the coalesce operator in VB. It is of the general form:

If(Expression ?? NullResult)

To use the coalesce operator, the value variable in the above examples must be a nullable type or a reference type. If value is non-null, value is returned. If value is null (Nothing in VB), the second parameter is returned. Here are some examples:

  • value = “CA”                                            result = “CA”
  • value = String.Empty                               result = String.Empty
  • value = null (nothing in VB)                     result = “<Empty>”

Use the coalesce operator any time you need to have special case handling of a null.

Ternary Operator

The ternary operator, also called the conditional operator, is new in VB 9 and has been in C# from the beginning. For a general description of a ternary operator, see this.

The ternary operator involves three expressions (hence the name ternary). The first expression is a Boolean expression that evaluates to true or false. The second expression is the result if the first expression is true. The third expression is the result if the first expression is false.

In C#:

string result = value == null ? "<Empty>" : "<Full>";

? is the ternary operator in C#. It is of the general form:

Condition ? TrueResult : FalseResult

In VB:

Dim result As String = If(value Is Nothing, "<Empty>", "<Full>")

The If keyword is used as the ternary operator in VB. It is of the general form:

If(Condition, TrueResult, FalseResult)

NOTE: In VB, you should always favor the ternary If() syntax over the old IIF syntax, which was inefficient and not recommended for use.

In the examples, if first expression is true, the second expression is returned. If the first expression is false, the third expression is returned. Here are some examples:

  • value = “CA”                                            result = “<Full>”
  • value = String.Empty                               result = “<Full>”
  • value = null (nothing in VB)                     result = “<Empty>”

Use the ternary operator any time you need two different results depending on a Boolean expression.

Enjoy!

with 3 comment(s)
Filed under: , , ,

If you are using Visual Studio 2008 (.NET Framework 3.5) or newer, you can leverage inferred typing. Inferred typing allows the compiler to infer your data types instead of you having to explicitly define the type. Variables that use inferred typing are still strongly typed, they just depend on .NET for determining (or inferring) the type.

Inferred typing is sometimes referred to as “duck typing”. This is from the truth that if something looks like a duck and quacks like a duck, it must be a duck. For more general information on inferred typing (or type inference), see this.

The compiler infers your data type based on its context. Here are some examples.

In C#:

var i = 1;

var x = "Hello";

var arr = new int[] {1, 2, 3, 4};

var c = new Customer();

var y = null; // Generates an error

To use inferred typing in C#, use the var keyword as in the above examples. Note that var is not short for variant and does not imply that the variables are weakly typed. Rather, the var keyword defines that the compiler should infer the type based on the context.

In VB:

Dim i = 1

Dim x = "Hello"

Dim arr() = New Integer() {1, 2, 3, 4}

Dim c = new Customer()

Dim y = Nothing

To use inferred typing in VB, you need to set Option Infer On:

  1. Double-Click on My Project under Solution Explorer.
  2. Click the Compile tab.
  3. Set Option Infer On.

If Option Infer is On, simply Dim the variable to take advantage of inferred typing. This does not imply that the variables are weakly typed or are of type Object. Rather, using Dim without a data type when Option Infer is On defines that the compiler should infer the type based on the context.

In the first example under either language, the inferred type of i is integer because it is assigned to 1. In the second example, x is inferred to be of type string. The arr variable is inferred to be of an integer array type.

The fourth example infers the type of c to be Customer and creates a new instance of the Customer class. This is less necessary as a shortcut in VB where you can also do this:

Dim c as New Customer

In the last example, VB infers the type of y to be Object. C# won’t attempt to infer the type and instead generates a compile error.

You can view the type that is inferred by hovering over the value in the Visual Studio text editor. In VB, hover over the variable. In C#, hover over the var keyword.

BUT – is this a good idea? Should you be leaving it up to the compiler to define your types?

The recommended best practice regarding inferred typing is as follows:

  • Do NOT use inferred typing for variables of intrinsic types (like in the above integer and string examples).
  • DO use inferred typing when creating a new instance, such as in the new Customer example.
  • DO use inferred typing when dealing with anonymous types (see this or this).
  • DO use inferred typing when working with LINQ and Lambda expressions.

Though a quick Bing of this topic shows that not all developers agree with these guidelines.

A common use of inferred typing is with LINQ and Lambda expressions because the data types of these expressions are often long and tedious.

For example:

In C#:

List<int> myList = new List<int>() { 1, 6, 8, 3, 2, 4, 15 };

var result = from item in myList
               where item < 5
               select item;

In VB:

Dim myArr() As Integer = {1, 6, 8, 3, 2, 4, 15}
Dim myList As List(Of Integer) = myArr.ToList

Dim result = From item In myList _
                Where item < 5 _
                Select item

NOTE: VB does not support List initializers but it does support array initializers. So to simplify the amount of code, an array initializer was used and then the array was converted to a List.

In both cases, the result variable type is System.Collections.Generic.IEnumerable<int>. Yuck!

Enjoy!

with 2 comment(s)
Filed under: , , , ,

There are situations where you may want to know who called a particular method in your application. Maybe you want to log it or use the information for debugging purposes. Whatever the reason, you can get information on the call stack using the StackTrace class.

A stack trace is basically information on the active stack while a program is running. See this for more basic information on stack trace.

The .NET StackTrace class is detailed here. This post provides some examples of using the StackTrace class to obtain information about the call stack.

If you want to view the entire StackTrace, you can loop through each stack frame.

NOTE: Be sure to set a reference to System.Diagnostics.

In C#:

private string MyMethod(string s1)
{

  StackTrace st = new StackTrace();
  foreach (StackFrame sf in st.GetFrames())
  {
    Debug.WriteLine("Calling class name: " +
                                sf.GetMethod().ReflectedType.Name);
    Debug.WriteLine("Calling method name: " + sf.GetMethod().Name);
    Debug.WriteLine("Full method signature: " +
                                        sf.GetMethod().ToString());

  } 
  return String.Empty;
}

In VB:

Public Function MyMethod(ByVal s1 As String) As String
  Dim st As New StackTrace
  For Each sf As StackFrame In st.GetFrames
    Debug.WriteLine("Calling class name: " & _
                                sf.GetMethod.ReflectedType.Name)
    Debug.WriteLine("Calling method name: " & sf.GetMethod.Name)
    Debug.WriteLine("Full method signature: " & sf.GetMethod.ToString)
  Next
  Return String.Empty
End Function

The GetMethod function gets the method executing in the defined stack frame. The 0th stack frame is the currently executing method. The 1st stack frame is the method that called it, the 2nd stack frame is the method that called that method, and so on up the call stack.

The Name property defines the name of the method. The ReflectedType property defines the class of the calling method and the ReflectedType.Name property provides the name of that class.

The ToString method provides the full method signature. Regardless of the language you are using, the signature will always be displayed in C# style syntax.

For example, the first few stack frames from my example application are as follows:

Calling class name: TestWin
Calling method name: MyMethod
Full method signature: System.String MyMethod(System.String)

Calling class name: TestWin
Calling method name: WorkWithMethods
Full method signature: Void WorkWithMethods()

Calling class name: TestWin
Calling method name: TestWin_Load
Full method signature: Void TestWin_Load(System.Object, System.EventArgs)

If you only want information about the direct caller, you can access the first stack frame.

In C#:

StackTrace st = new StackTrace();
StackFrame sfFirst = st.GetFrame(1);
Debug.WriteLine("Calling class name: " +
                            sfFirst.GetMethod().ReflectedType.Name);
Debug.WriteLine("Calling method name: " + sfFirst.GetMethod().Name);

In VB:

Dim st As New StackTrace
Dim sfFirst As StackFrame = st.GetFrame(1)
Debug.WriteLine("Calling class name: " & _
                            sfFirst.GetMethod.ReflectedType.Name)
Debug.WriteLine("Calling method name: " & sfFirst.GetMethod.Name)

This code provides the name of the class and method that directly called the current method:

Calling class name: TestWin
Calling method name: WorkWithMethods

Enjoy!

with 1 comment(s)
Filed under: , , , ,

There are often times that you need to find specific controls on a Windows Form (WinForm). For example, you may want to find all of the TextBoxes to clear their contents, set their background color, or hook up events. You may want to find all of the checkboxes to check or uncheck them. Or find all of the buttons to hook up events or change their color.

Since a Windows Form has a Controls collection, it may seem obvious to simply loop through the Control's collection to find all of the desired controls.

But many of the controls provided in WinForms can contain other controls. Take this form for example:

image

This form contains two Panel controls. The top Panel control contains several Label controls and a ComboBox. The bottom Panel control contains Labels and TextBoxes.

If you look at the Controls collection for the form in this example, you find that it contains only two controls. Only the two Panel controls are actually on the form. The other controls are on the Panel controls. So to find those controls, you need to loop through the Controls collection of each Panel.

If you put a Tab control on a Panel control, then the TextBox controls would be on the Tab control which is on the Panel control. The easiest way to look for controls that could be on other controls which in turn could be on other controls, is to use recursion.

Recursion is basically calling a method from the method itself. (You can find a more detailed explanation here.) The following demonstrates recursion to find all of the TextBoxes on a form.

In C#:

private void ProcessControls(Control ctrlContainer)
{
    foreach (Control ctrl in ctrlContainer.Controls)
    {
        if (ctrl.GetType() == typeof(TextBox))
        {
            // Do whatever to the TextBox
        }

        if (ctrl.HasChildren)
            ProcessControls(ctrl);
    }
}

In VB:

Private Sub ProcessControls(ByVal ctrlContainer As Control)
    For Each ctrl As Control In ctrlContainer.Controls
        If TypeOf ctrl Is TextBox Then
            ' Do whatever to the TextBox
        End If

        ' If the control has children,
        ' recursively call this function
        If ctrl.HasChildren Then
            ProcessControls(ctrl)
        End If
    Next
End Sub

The code begins by processing each control within the defined container control using the container’s Controls collection. The control’s type is checked to determine if it is the desired type of control. This technique can be used to look for any type of control or multiple types of controls.

The HasChildren property of the control is checked to determine if the control itself is a container for other controls. If so, it calls this method recursively using the control as the container control.

This method resides in the Form and is called as follows.

In C#:

ProcessControls(this);

In VB:

ProcessControls(Me)

The form itself is passed into the ProcessControls method as the highest level container. If you only want to search for controls within some other container, pass it instead of the entire form. For example, if you only wanted to search for TextBox controls on Panel2 or on controls that are on Panel2, you would pass Panel2 instead of the form (this or me).

Finding Controls By Name

If you want to find a control or set of controls by name, there is an easier way.

In C#:

Control[] ctrls = this.Controls.Find("TextBox1", true);

In VB:

Dim ctrls() As Control = Me.Controls.Find("TextBox1", True)

This code uses the Find method of the controls collection. The first parameter defines the name of the control(s) to find. If the second parameter is true, the Find will search through all child controls, performing the recursive operation for you. If it is false, it will only look through the form’s Controls collection.

When I was first working with the Find method, I thought it odd that it returned an array since you can only put one control on the form named “TextBox1”. However, you can add multiple controls with the same name using Controls.Add. Also, if you build a composite user control you can also put a TextBox1 on the user control. So if you also have a TextBox1 on the form, the Find method will find two controls named “TextBox1”.

Enjoy!

with 5 comment(s)
Filed under: , , , ,

IMG_2010

I remember watching Doctor Who in college when Tom Baker was the doctor. Those were the days before VCR recording and TiVo, so we had to rush home to ensure we caught that next episode.

Tom Baker’s Doctor Who was smart, logical, and a little crazy. And always with the striped double-length scarf.

 

IMG_2003

 

 

I was thrilled when I found out that the BBC was starting Doctor Who again and we added it to our TiVo choices. I got my oldest daughter so hooked on it that she found most of the old episodes on the internet (even some from the 1960’s) and watched them.

So it was not surprising that we took the opportunity to include Doctor Who in our trip to London in January, 2009.

 

 

Our first Doctor Who stop was a special exhibit at Earl’s Court in London. My daughter even got a brief ride in the TARDIS….

 IMG_2022

IMG_2016

 

… and had to fight off the Dalek. Exterminate!!!

Second stop was the Doctor Who Exhibition in Cardiff.

IMG_2068 IMG_2070

IMG_2124

 

 

 

Final stop was seeing the current Doctor Who (David Tennant) in Hamlet. (No cameras allowed during the play, so we just took a picture with the play bill during intermission.) 

 

 

 

 

Are you a Doctor Who fan? Share your thoughts on Doctor Who.

with 5 comment(s)
Filed under:

Last night, my husband (who is also a .NET developer) asked me about sorting a DataTable by user-defined columns. (Yes, we are a pretty exciting couple!)

Most developers that work with DataTables know how to sort the DataTable using a DataView.Sort. If you are interested in that technique, you can view the msdn documentation here. This post is about sorting using Linq.

NOTE: Be sure to set a reference to System.Data.DatasetExtensions.

If you know ahead of time which columns of the DataTable should be sorted, you can use LINQ to DataTables like this.

In C#:

var query = from c in dt.AsEnumerable()
            orderby c.Field<DateTime?>("LastPurchaseDate"),
                    c.Field<string>("LastName") descending
            select c;
DataView dv   = query.AsDataView();

In VB:

Dim query = From c In dt.AsEnumerable _
            Order By c.Field(Of DateTime?)("LastPurchaseDate"), _
                     c.Field(Of String)("LastName") Descending
Dim dv As DataView = query.AsDataView

The dt variable represents the table you wish to sort. The AsEnumerable is an extension method on the DataTable that allows you to use Linq with it. The Order By clause takes any number of columns. You must specify the data type and then the column name or index. To sort descending, use the Descending keyword.

A nullable DateTime (represented by DateTime?) ensures that the code correctly handles any null values in the table.

In the above examples, the data is sorted first by LastPurchaseDate (ascending) and then by LastName (descending).

The resulting DataView can be bound to a grid or other control.

If you prefer Lambda expressions, you can do this same sort as follows:

In C#:

var query2 = dt.AsEnumerable()
          
.OrderBy(c=> c.Field<DateTime?>("LastPurchaseDate"))
           .ThenByDescending(c=> c.Field<string>("LastName"));
DataView dv2   = query.AsDataView();

In VB:

Dim query2 = dt.AsEnumerable _
    .OrderBy(Function(c) c.Field(Of DateTime?)("LastPurchaseDate")) _
    .ThenByDescending(Function(c) c.Field(Of String)("LastName"))
Dim dv2 As DataView = query.AsDataView

This code performs the same sort as the prior examples.

But neither of these techniques work well if you want the user to select any number of columns to use for the sort. In that case, you need something a little more full-featured.

The first step to building a more generalized sort is to build your own comparer class.

In C#:

private class RowComparer : IComparer<DataRow>
{
    public Dictionary<int, SortOrder> SortColumns { get; set; }

    public int Compare(System.Data.DataRow x, System.Data.DataRow y)
    {
        int returnValue  = 0;
        foreach (int key in SortColumns.Keys)
        {
            int compareResult ;

            // Check for nulls
            if (x.ItemArray[key] == DBNull.Value
                       && y.ItemArray[key] == DBNull.Value)
                compareResult = 0;
            else if (x.ItemArray[key] == DBNull.Value)
                compareResult = -1;
            else if (y.ItemArray[key] == DBNull.Value)
                compareResult = 1;
            else
            {
                // Execute the compare based on the column type
                if (x.Table.Columns[key].DataType.Name ==
                                            typeof(Decimal).Name)
                    compareResult =
                         Decimal.Compare((decimal)x.ItemArray[key],
                                         (decimal)y.ItemArray[key]);

                else if (x.Table.Columns[key].DataType.Name ==
                                            typeof(DateTime).Name)
                    compareResult =
                         DateTime.Compare((DateTime)x.ItemArray[key],
                                          (DateTime)y.ItemArray[key]);
                else
                    // Compare anything else as a string
                    compareResult =
                         String.Compare(x.ItemArray[key].ToString(),
                                        y.ItemArray[key].ToString());
            }

            if (compareResult != 0)
            {
                returnValue =
                   SortColumns[key] == SortOrder.Ascending ?
                                  compareResult: -compareResult;
                break;
            }
        }
        return returnValue;
    }
}

In VB:

Public Class RowComparer
    Implements IComparer(Of DataRow)

    Private _sortColumns As Dictionary(Of Integer, SortOrder)
    Public Property SortColumns() As Dictionary(Of Integer, SortOrder)
        Get
            Return _sortColumns
        End Get
        Set(ByVal value As Dictionary(Of Integer, SortOrder))
            _sortColumns = value
        End Set
    End Property

    Public Function Compare(ByVal x As System.Data.DataRow, _
         ByVal y As System.Data.DataRow) As Integer _
         Implements System.Collections.Generic.IComparer( _
                            Of System.Data.DataRow).Compare
        Dim returnValue As Integer = 0
        For Each key As Integer In SortColumns.Keys
            Dim compareResult As Integer

            ' Handle DBNull.
            Dim xValue As Object = _
              If(x.Item(key) Is DBNull.Value, Nothing, x.Item(key))
            Dim yValue As Object = _
              If(y.Item(key) Is DBNull.Value, Nothing, y.Item(key))

            ' Execute the appropriate compare based on the column type
            Select Case x.Table.Columns(key).DataType.Name
                Case GetType(Decimal).Name
                    compareResult = _
                        Decimal.Compare(CType(xValue, Decimal), _
                                      
CType(yValue, Decimal))

                Case GetType(DateTime).Name
                    compareResult = _
                        DateTime.Compare(CType(xValue, DateTime), _
                                         CType(yValue, DateTime))

                Case Else
                    ' Compare anything else as a string
                    compareResult = _
                         String.Compare(x.Item(key).ToString, _
                                        y.Item(key).ToString)
            End Select

            If compareResult <> 0 Then
                returnValue = _
                  If(SortColumns(key) = SortOrder.Ascending, _
                          compareResult, -compareResult)
                Exit For
            End If
        Next
        Return returnValue

    End Function
End Class

This class defines a comparer to use when sorting a DataRow. The sortColumns property is a Dictionary that stores the index of the column to use as the sort, and a SortOrder to define whether to sort ascending or descending. Presumably, the values for this Dictionary were obtained from the user.

The Compare method does all of the work. It compares any two DataRows to determine how each row should be sorted against any other row. The method processes each of the sortColumns.

First, it checks for a DBNull. The DBNull checking is different in the C# code and VB code. The C# code checks for a DBNull and manually sets the result of the compare. The VB code simply sets the value to Nothing if it is DBNull.

The code performs a compare based on the type of column. This is necessary because the user would expect decimals to sort as numbers, not as strings. You can add any other data types here as you require. Any data types not specifically handled will be handled as a string.

Note that a Select/Case statement was used in VB, but if/else if was used in C#. This is because C# requires its switch/case statements to switch based on constant values, not variables.

Regardless of the datatype, the result of the type-specific compare method is:

  • -1: If the value of x is less than the value of y.
  • 1: if the value of x is great than the value of y.
  • 0: if the value of x and y are equal.

If the resulting value is not 0, the loop can exit because the comparison is complete. If the resulting value is 0, meaning the columns are equal, the loop continues and the next column in the set of sort columns is checked.

You then use this class as follows.

In C#:

Dictionary<int, SortOrder> sortColumns =
                    new Dictionary<int, SortOrder>();
sortColumns.Add(2,SortOrder.Ascending);
sortColumns.Add(1, SortOrder.Descending);

RowComparer comp = new RowComparer();
comp.SortColumns = sortColumns;

var query3 = dt.AsEnumerable().OrderBy(q => q, comp);
DataView dv3 = query3.AsDataView();

In VB:

Dim sortColumns As New Dictionary(Of Integer, SortOrder)
sortColumns.Add(2, SortOrder.Ascending)
sortColumns.Add(1, SortOrder.Descending)

Dim comp As New RowComparer
comp.SortColumns = sortColumns

Dim query3 = dt.AsEnumerable.OrderBy(Function(q) q, comp)
Dim dv3 As DataView = query3.AsDataView

The first set of code sets up the Dictionary of sort columns. The sort columns are hard-coded in this example, but presumably they would come from the user.

The code then creates an instance of the new RowComparer class and passes in the set of columns.

NOTE: You could define a parameterized constructor and pass in the sorted columns instead of using a property, if desired.

The Lambda expression to perform the sort is then greatly simplified. It just passes in the desired comparer.

That’s it. You now have the ability to sort your DataTable using any user-defined set of columns.

Enjoy!

with 3 comment(s)
Filed under: , , , , , ,

As with most things in Visual Studio, there are many ways to export data from your .NET application to an Excel spreadsheet. This post covers one straightforward technique.

First, set a reference to the desired version of the Microsoft Excel Object Library from the COM tab of the Add Reference dialog. The resulting reference appears as Microsoft.Office.Interop.Excel.

Import the Microsoft.Office.Interop.Excel library in your code file. To keep the namespaces clear, consider using an alias.

In C#:

using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;

NOTE: C# also requires a reference to System.Reflection to use the Missing.Value field. This is required because C# 3.0 does not support optional parameters. Every parameter must have a value, so Missing.Value is used to provide a value for all unspecified optional parameters.

In VB:

Imports Excel = Microsoft.Office.Interop.Excel

The rest of the code is presented in VB and C# and then discussed below. It is provided in one big chunk to make it easier to copy/paste into your application.

In C#:

Excel.Application oXL;
Excel.Workbook oWB;
Excel.Worksheet oSheet;
Excel.Range oRange;

// Start Excel and get Application object.
oXL = new Excel.Application();

// Set some properties
oXL.Visible = true;
oXL.DisplayAlerts = false;

// Get a new workbook.
oWB = oXL.Workbooks.Add(Missing.Value);

// Get the active sheet
oSheet = (Excel.Worksheet)oWB.ActiveSheet ;
oSheet.Name = "Customers";

// Process the DataTable

// BE SURE TO CHANGE THIS LINE TO USE *YOUR* DATATABLE

DataTable dt = Customers.RetrieveAsDataTable();

int rowCount = 1;
foreach (DataRow dr in dt.Rows)
{
    rowCount += 1;
    for (int i = 1; i < dt.Columns.Count+1; i++)
    {
        // Add the header the first time through
        if (rowCount==2)
        {
            oSheet.Cells[1, i] = dt.Columns[i - 1].ColumnName;
        }
        oSheet.Cells[rowCount, i] = dr[i - 1].ToString();
    }
}

// Resize the columns
oRange = oSheet.get_Range(oSheet.Cells[1, 1],
              oSheet.Cells[rowCount, dt.Columns.Count]);
oRange.EntireColumn.AutoFit();

// Save the sheet and close
oSheet = null;
oRange = null;
oWB.SaveAs("test.xls", Excel.XlFileFormat.xlWorkbookNormal,
    Missing.Value, Missing.Value, Missing.Value, Missing.Value,
    Excel.XlSaveAsAccessMode.xlExclusive,
    Missing.Value, Missing.Value, Missing.Value,
    Missing.Value, Missing.Value);
oWB.Close(Missing.Value, Missing.Value, Missing.Value);
oWB = null;
oXL.Quit();

// Clean up
// NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

In VB:

Dim oXL As Excel.Application
Dim oWB As Excel.Workbook
Dim oSheet As Excel.Worksheet
Dim oRange As Excel.Range

' Start Excel and get Application object.
oXL = New Excel.Application

' Set some properties
oXL.Visible = True
oXL.DisplayAlerts = False

' Get a new workbook.
oWB = oXL.Workbooks.Add

' Get the active sheet
oSheet = DirectCast(oWB.ActiveSheet, Excel.Worksheet)
oSheet.Name = "Customers"

' Process the DataTable
'
BE SURE TO CHANGE THIS LINE TO USE *YOUR* DATATABLE
Dim dt As Data.DataTable = Customers.RetrieveAsDataTable

' Create the data rows
Dim rowCount As Integer = 1
For Each dr As DataRow In dt.Rows
    rowCount += 1
    For i As Integer = 1 To dt.Columns.Count
        ' Add the header the first time through
        If rowCount = 2 Then
            oSheet.Cells(1, i) = dt.Columns(i - 1).ColumnName
        End If
        oSheet.Cells(rowCount, i) = dr.Item(i - 1).ToString
    Next
Next

' Resize the columns
oRange = oSheet.Range(oSheet.Cells(1, 1), _
          oSheet.Cells(rowCount, dt.Columns.Count))
oRange.EntireColumn.AutoFit()

' Save the sheet and close
oSheet = Nothing
oRange = Nothing
oWB.SaveAs("test.xls")
oWB.Close()
oWB = Nothing
oXL.Quit()

' Clean up
' NOTE: When in release mode, this does the trick
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()

NOTE: The VB code code above is set up to run with Option Strict ON.

The code begins by declaring the variables used in the code. Notice that in both C# and in VB this makes use of the namespace alias as shown at the very beginning of this post.

The code then starts Excel and makes it visible. If you want to create the spreadsheet without making Excel visible to the user, you could set the Visible to false instead.

A workbook is added, the active sheet of the workbook is referenced, and the sheet is given a name.

The DataTable is then processed. You can use any DataTable here. The code loops through the rows of the DataTable and for each row it loops through the columns. The first time through the columns it adds column headings.

To make the spreadsheet easier to read, the columns are then resized to show the contents of the cells. If you have lots of data in a cell, you may want to skip this step.

The workbook is then saved. Since no directory was specified, Excel will save the file to your My Documents folder. Notice the massive number of parameters in the C# code when calling the SaveAs (and Close) methods. This is required because C# does not have optional parameters. So the code must fill in each and every parameter in the call. VB does support optional parameters, so does not need to set the extra parameters.

NOTE: C# is getting optional parameters in .NET 4.0 (VS 2010).

Finally, the code is cleaned up. Since Excel is accessed through COM interop, the double garbage collection code is added to ensure Excel is correctly closed.  See this link for more information on why this specific garbage collection code is recommended.

Enjoy!

with 16 comment(s)
Filed under: , , , , ,

There are many things in VB.NET and C# that are very similar. So if you know that basic syntax of both languages, it is relatively easy to convert a few lines of code from one to the other.

Not so much with events. This is one area where it is easy to get tripped up when you have to convert code from one language to the other.

To demonstrate how to define events in VB vs C#, this example is a WinForms user control that raises a single custom event. It is a very simple user control that inherits from a TextBox. The point is not to demonstrate the proper way to create a user control, but rather just to focus on adding an event to the user control.

The event is a ValueChanged event. This event is raised when the associated TextBox TextChanged event occurs. While this might be less than useful, it provides a simple example of defining event in VB and C#.

One other note before diving into the code. The event in this post follows the suggested pattern of defining event parameters of type object and eventArgs (or a subclass thereof). If you are coming from earlier versions of VB or C, you may be used to creating events with other types of parameters.

For example, you could do this in VB:

Public Class SampleUserControl
  ' Declare an event
  Public Event ValueChanged(value as Integer)

  Private Sub SampleUserControl_TextChanged(ByVal sender As Object, _
              ByVal e As System.EventArgs) Handles Me.TextChanged

     ' Raise the event
     RaiseEvent ValueChanged(42)
  End Sub
End Class

While this makes the coding extremely easy (what you see above is all you need), it does not follow the pattern or best practices as defined by the “Framework Design Guidelines”.

So lets get to the real example. Since the code is so different, instead of showing a little at a time in both languages, this shows the VB code first. Then it goes through the same features using C#.

In VB:

Public Class SampleUserControl
  ' Declare an event
  Public Event ValueChanged(ByVal sender As Object, _
                  ByVal e As ValueChangedEventArgs)

  Private Sub SampleUserControl_TextChanged(ByVal sender As Object, _
                  ByVal e As System.EventArgs) Handles Me.TextChanged
        Dim tb As TextBox = DirectCast(sender, TextBox)
        Dim value As Integer = 0
        If IsNumeric(tb.Text) Then
                value = CType(tb.Text, Integer)
        End If
       ' Raise the event
        RaiseEvent ValueChanged(sender, _
                  New ValueChangedEventArgs(value))
  End Sub
End Class

This class is a user control that inherits from a TextBox. In VB, by convention the inherits statement is in the associated designer file (SampleUserControl.Designer.vb):

<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class SampleUserControl
    Inherits TextBox
    …

End Class

The code in the class first declares the event. Notice that it uses the standard sender and e parameters. It then raises the event when the associated TextBox text is changed. If the TextBox Text property value is numeric, it passes the value as part of the event arguments; otherwise, it passes 0.

Since the ValueChanged event required additional data associated with it, it did not use the standard EventArgs class. Rather, it defined its own ValueChangedEventArgs class:

Public Class ValueChangedEventArgs
    Inherits EventArgs

    Private _NewValue As Integer
    Public Property NewValue() As Integer
        Get
            Return _NewValue
        End Get
        Set(ByVal value As Integer)
            _NewValue = value
        End Set
    End Property

    Public Sub New(ByVal currentNewValue As Integer)
        NewValue = currentNewValue
    End Sub

End Class

This class inherits from EventArgs. Any desired properties can be added to this class. In this case, only one property was added for the new value. The constructor requires that this new value be passed in as a parameter.

So using a specialized EventArgs class required a little more code than the first VB example. But this is still basically straightforward.

To summarize the steps:

  1. Declare the event in the User Control.
  2. Raise the event in the User Control where appropriate.
  3. Define the class that inherits from EventArgs.

So now let’s move on to C#:

// Declare a delegate
public delegate void ValueChangedEventHandler(object sender,
                                  ValueChangedEventArgs e);

public partial class SampleUserControl : TextBox
{

    public SampleUserControl()
    {
        InitializeComponent();
    }

    // Declare an event
    public event ValueChangedEventHandler ValueChanged;

    protected virtual void OnValueChanged(ValueChangedEventArgs e)
    {
        if (ValueChanged != null)
            ValueChanged(this,e);
    }

    private void SampleUserControl_TextChanged(object sender,
                                     EventArgs e)
    {
        TextBox tb  = (TextBox)sender;
        int value;
        if (!int.TryParse(tb.Text, out value))
            value = 0;
        // Raise the event
       OnValueChanged( new ValueChangedEventArgs(value));
    }

}

This code declares a delegate outside of the class. The class is a user control that inherits from a TextBox.

The first set of code is the constructor for the user control. The code then declares the event using the delegate.

Since there is no RaiseEvent function in C#, you have to create a method that generates your event. This is the OnValueChanged method shown above. The convention is to use the word “On” and the name of your event. The TextBox TextChanged event then calls OnValueChanged to raise the event.

Since the ValueChanged event required additional data associated with it, it did not use the standard EventArgs class. Rather, it defined its own ValueChangedEventArgs class:

public class ValueChangedEventArgs : EventArgs
{
    public int NewValue { get; set; }

    public ValueChangedEventArgs(int newValue)
    {
        NewValue = newValue;
    }
}

This code is very similar to the VB code. This class inherits from EventArgs. Any desired properties can be added to this class. In this case, only one property was added for the new value. The constructor requires that this new value be passed in as a parameter.

To summarize the steps:

  1. Declare a delegate outside of the user control class.
  2. Declare the event in the User Control of the delegate type.
  3. Create a method to generate the event.
  4. Call the method to raise the event in the User Control where appropriate.
  5. Define the class that inherits from EventArgs.

When a form uses your user control, it can hook up the event as needed.

In VB:

Private Sub SampleUserControl1_ValueChanged(ByVal sender As Object, _
                           ByVal e As ValueChangedEventArgs) _
                           Handles SampleUserControl1.ValueChanged
    Debug.WriteLine("Made it to the event test: " & e.NewValue)
End Sub

This code works because in the designer, the user control is automatically declared WithEvents:

Friend WithEvents SampleUserControl1 As _
                               SampleApplication.SampleUserControl

Alternatively, you can use the Addhandler statement to hook up the ValueChanged event.

In C#:

private void sampleUserControl1_ValueChanged(object sender, 
                           ValueChangedEventArgs e)
{
    Debug.WriteLine("Made it to the event test: " + e.NewValue);
}

For this code to work, you need to use the designer to hook up the events, (which generates the following line of code in the designer file) or add this line of code in the form:

sampleUserControl1.ValueChanged += sampleUserControl1_ValueChanged;

So C# and VB can provide the exact same functionality, but with different steps to get there.

Enjoy!

with 2 comment(s)
Filed under: , , , ,

I learned to program using structured programming techniques with RSTS/E BASIC on a PDP 11/45 and progressed to VAX BASIC on a VAX 11/70. This was before database products were common, so we created our own data structures using hash tables and linked lists. Then came Ingres, and we moved to a relational database system.

After a brief stint on the mainframe, I moved to Silicon Valley and started using C and then C++. This was so long ago, that there was no Microsoft C++ so we used Zortek C++. This was my first introduction to object-oriented programming and I really liked it.

In 1992, I left the corporate world and moved into consulting. One of my first consulting projects was with Alan Cooper, the “father of Visual Basic”. At that point I was introduced to VB 2. It was so similar to what I had done on the VAX, that everything seemed familiar. The only thing I could not figure out how to do was to instance the user-defined types (UDTs). Come to find out, you could not. No real object-oriented programming features were available until VB 4.0.

When .NET first came out, I moved to VB.NET. There was so much that was different, it was nice to at least have some syntax that was familiar.

After spending months and months with .NET, I finally got to the point where I could actually code without looking something up every two minutes.

At that point I thought it prudent to learn C#. That allowed me to more easily attend talks or view sample code that was in C#. With my C++ background, some syntax was still familiar, but much was not (like event handling!).

Over the past few years, I have been doing more C# code, almost exclusively these past few months. I have found some things that I much prefer doing in C#, while others I much prefer VB.

But my clients still ask the question, “Should we move our team to VB.NET or C#?” Here is my reply:

  • If you are coming from C or C++ or Java, it is much more natural to move to C#.
  • If you are coming from VB 6 (or prior) or from VBA, it is much more natural to move to VB.NET.
  • If you are doing lots of “cutting edge” coding, you may want to consider using C# because often examples for cutting-edge topics come out first in C#. (Though Microsoft is working hard to change this. Look at LINQ as an example, the VB and C# 101 examples came out about the same time.)
  • If you are doing lots of XML coding, you may want to consider using VB because it has amazing XML features that C# does not have.

I also recommend that everyone get at least a reading knowledge of both languages. Then if an example is in one or the other, or if you go to a presentation in one or the other, you get the basic idea of what is going on.

with 5 comment(s)
Filed under: , ,

When displaying text to the user as a title or message, you may want to ensure that the text is presented in proper case (also called title case).

If you are not familiar with the term “title case”, it basically means upper casing the first letter of each word as you would do in a proper title. For example: “Last Name” or “Customer Status Code”.

NOTE: Be sure to define a reference to System.Globalization.

In C#:

string sampleString = "this is a title";
CultureInfo currentCulture =
         System.Threading.Thread.CurrentThread.CurrentCulture;
TextInfo currentInfo = currentCulture.TextInfo;
sampleString = currentInfo.ToTitleCase(sampleString);

In VB:

Dim sampleString As String = "this is a title"
Dim currentCulture As CultureInfo = _
         System.Threading.Thread.CurrentThread.CurrentCulture
Dim currentInfo As TextInfo = currentCulture.TextInfo
sampleString = currentInfo.ToTitleCase(sampleString)

This code uses the CultureInfo to ensure that the casing is set appropriately based on the current culture. It then uses the ToTitleCase method of the TextInfo class to convert to the appropriate casing.

In VB there is another option to accomplish this task:

Dim sampleString As String = "this is a title"
sampleString = StrConv(sampleString, vbProperCase)

This code uses the StrConv function that is part of the Microsoft.VisualBasic namespace. This function could also be used from C# if you define a reference to the Microsoft.VisualBasic namespace.

The result is:

This Is A Title

Notice that this does not follow the proper case rules that define articles and conjunctions as lower case. Rather, it converts the first letter of every word to upper case and the remainder of the text to lower case.

Enjoy!

with 6 comment(s)
Filed under: , , ,

This post describes how to populate a WinForms TreeView control from an XML file assuming you are targeting the .NET Framework Version 3.5.

The XML file used in this example looks like this:

<States>
  <State name="California">
    <Regions>
      <Region name="San Luis Obispo">
        <Area name="Santa Maria" />
        <Area name="Seaside" />
      </Region>
      <Region name="Silicon Valley">
        <Area name="San Jose"/>
        <Area name="Sunnyvale"/>
      </Region>
    </Regions>
  </State>
  <State name="Wisconsin">
    <Regions>
      <Region name="Milwaukee">
        <Area name ="Mukwanago"/>
        <Area name="Germantown"/>
      </Region>
      <Region name="Fox Valley">
        <Area name="Oshkosh" />
        <Area name="Appleton" />
      </Region>    
    </Regions>
  </State>
</States>

The TreeView will display the following:

State
--- Region
------ Area

The code is provided here in VB and C# and then described in detail below.

NOTE: Be sure to set a reference to System.Core and System.Xml.Linq

In C#:

XElement doc  = XElement.Load("testXML.xml");

TreeNode stateNode;
TreeNode regionNode;
foreach (XElement state  in doc.Descendants("State"))
{
    stateNode = treeView1.Nodes.Add(state.Attribute("name").Value);
    foreach (XElement region in state.Descendants("Region"))
    {
        regionNode =
            stateNode.Nodes.Add(region.Attribute("name").Value);
        foreach (XElement area in region.Descendants("Area"))
        {
            regionNode.Nodes.Add(area.Attribute("name").Value);
        }
    }
}

In VB:

Dim doc As XElement = XElement.Load("testXML.xml")

Dim stateNode As TreeNode
Dim regionNode As TreeNode
For Each state As XElement In doc...<State>
    stateNode = TreeView1.Nodes.Add(state.@name)
    For Each region As XElement In state...<Region>
        regionNode = stateNode.Nodes.Add(region.@name)
        For Each area As XElement In region...<Area>
            regionNode.Nodes.Add(area.@name)
        Next
    Next
Next

In both cases, the XML is first retrieved from a file. An in-memory XML string could be used instead.

Three for/each loops are used, one for each level of the TreeView hierarchy.

The states are processed first. A node is added for each state name.

The regions are processed next. A node is added under the state for each region name.

Finally, the areas are processed. A node is added under the region for each area name.

Notice that the VB code leverages XML literals. The C# code uses XElements (which also work in VB).

Enjoy!

with 9 comment(s)
Filed under: , , , , ,

There are often times that you need to write out text files containing data managed by your application. For example, you may need to write out a file to be read by another system. In many cases, this file needs to have a particular format, with justified or aligned columns like this:

000001  Baggins             Bilbo     20090711 
000002  Baggins             Frodo     20090701 
000003  Gamgee              Samwise   20090720 
000004  Cotton              Rosie     20090721
 

The first column is the Id, padded with 0’s. The remaining columns are the last name, first name, and date of last edit in YYYYMMDD format.

NOTE: If you can format your text file as XML instead, see this.

The StringBuilder class provides a method that makes this formatting a snap.

In C#:

using System.IO;
using System.Text;

StringBuilder sb = new StringBuilder();

foreach (Customer c in Customers.Retrieve())
{
    sb.AppendFormat("{0,-8}{1,-20}{2,-10}{3,-10:yyyyMMdd}{4}",
                    c.CustomerId.ToString().PadLeft(6, '0'),
                    c.LastName,
                    c.FirstName,
                    c.LastEditDate,
                    Environment.NewLine);
}

File.WriteAllText("Test.txt", sb.ToString());

In VB:

Dim sb As New System.Text.StringBuilder

For Each c As Customer In custList
    sb.AppendFormat("{0,-8}{1,-20}{2,-10}{3,-10:yyyyMMdd}{4}", _
                    c.CustomerId.ToString.PadLeft(6, "0"c), _
                    c.LastName, _
                    c.FirstName, _
                    c.LastEditDate, _
                    Environment.NewLine)
Next

My.Computer.FileSystem.WriteAllText("Test.txt", sb.ToString, False)

The code starts by creating an instance of the StringBuilder class. It then loops through each customer in the list of customers. See this for the code that creates the customer list. NOTE: A LastEditDate was added to the Customer class (not shown) in order to demonstrate date formatting.

You do not have to have business objects to use this code. You could instead loop through a DataSet, DataTable, or DataReader to get the data for your file.

The key to formatting the data into columns is the AppendFormat method of the StringBuilder. This method uses the composite formatting features of the .NET framework to build the string. In this case, the formatting parameter is a set of indexed placeholders, called format items, that look like this:

{index, length:formatString}

The index matches the formatting string with the parameter that follows. For example, index 0 is the first parameter after the format string, which is the CustomerId. Index 1 is the second parameter after the format string, which is the LastName. And so on.

The optional length defines the amount of space that is provided for the column. The system will automatically pad the field with spaces to the defined width. A positive length value will align the text to the right. A negative value aligns the text to the left.

The optional formatString defines a standard or custom format string using the .NET formatting syntax.

The format items used in this example are as follows:

  • {0, –8}: Defines that the first parameter will fill 8 characters and will be left justified. This is the CustomerId. Notice that the PadLeft method is used by the CustomerId property to pad the Id field with zeros.
  • {1, –20}: Defines that the second parameter will fill 20 characters and will be left justified. This is the last name.
  • {2,-10}: Defines that the third parameter will fill 10 characters and will be left justified. This is the first name.
  • {3,-10:yyyyMMdd}: Defines that the fourth parameter will fill 10 characters and will be left justified. The value will be formatted as a date in YYYYMMDD format. This is the last edit date.
  • {4}: Defines that the fifth parameter will be used “as is”. This is the new line constant, which is inserted to ensure each customer is on a separate line.

The final part of the code uses the ToString method of the StringBuilder to convert the StringBuilder text to a string. It then writes that string to the defined file.

To read this file back into your application as an in-memory DataTable, see this link. To read the file back in using VB's TextFieldParser class, see this link.

Enjoy!

If you are building applications in .NET to manage data for a business, you are most likely creating business object classes. Depending on the business, these classes could include Customer, Product, Order, Invoice, PurchaseOrder, Employee, TimeCard and so on.

A simple sample Customer class is shown here.

There are some features that all business objects need to support. For example:

  • Entity State: The business object needs to track whether it is new, updated, or deleted so it can make the appropriate change to the database.
  • Validation: Is the current data defined for the business object valid?
  • Save: Saves the changes to the database.

Notice that data retrieval functionality is not included in this list. That is because in most cases, you may never retrieve a single business object. Rather, you would retrieve a set of them. For example, all active orders or all customers with overdue invoices. So the retrieve functionality is not included in the class that manages a single object. (Most on this in a later post.)

Instead of repeating this common functionality in each business object, it makes sense to build a base class. Each business object can then inherit from this base class to share this common functionality.

So let’s get started.

In C#:

using System;
using System.ComponentModel;

public abstract class BoBase :
        IDataErrorInfo,
        INotifyPropertyChanged
{

}

In VB:

Imports System.ComponentModel

Public MustInherit Class BOBase
    Implements IDataErrorInfo
    Implements INotifyPropertyChanged

End Class

The class is abstract (MustInherit in VB) to indicate that it is meant to be a base class and not to be used on its own. An abstract class cannot be instantiated directly, so no other code can create an instance of the class.

The class then implements two interfaces:

  • IDataErrorInfo: This interface provides error information that the user interface can use to report validation errors to the user. It works well with the ErrorProvider control provided in WinForms and supports ASP.NET and WPF features.
  • INotifyPropertyChanged: This interface ensures that the user interface is notified when a property value changes, keeping your business object and user interface in sync.

We’ll look at the implementation of these interfaces shortly.

First, define the valid set of entity states.

In C#:

protected internal enum EntityStateType
{
    Unchanged,
    Added,
    Deleted,
    Modified
}

In VB:

Protected Friend Enum EntityStateType
    Unchanged
    Added
    Deleted
    Modified
End Enum

The Enum is declared Protected because the entity state should only be accessible from the business object itself. However, Internal (Friend in VB) was added so that related objects (such as Order and OrderLineItem) could reference the related object state.

The business object state is retained using an EntityState property.

In C#:

protected EntityStateType EntityState { get; private set; }

In VB:

Private _EntityState As EntityStateType
Protected Property EntityState() As EntityStateType
    Get
        Return _EntityState
    End Get
    Private Set(ByVal value As EntityStateType)
        _EntityState = value
    End Set
End Property

This property is protected, but the setter is private. So the business objects that inherit from this class can read the entity state, but only the base class can set the value.

Additional properties provide a way to get the entity’s state in an easier fashion. These properties are not required, but they make the base class a little easier to use.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsDirty
{
    get { return this.EntityState != EntityStateType.Unchanged; }
}

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsNew
{
    get { return this.EntityState == EntityStateType.Added; }
}

In VB:

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsDirty() As Boolean
    Get
        Return Me.EntityState <> EntityStateType.Unchanged
    End Get
End Property

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsNew() As Boolean
    Get
        Return Me.EntityState = EntityStateType.Added
    End Get
End Property

The IsDirty and IsNew properties are public, so they can be accessed from anywhere. They are marked with two attributes:

  • Bindable: Defines whether the property should be used for binding. In this case the value is false because the user interface should not be able to bind to this property.
  • Browsable: Defines whether the property should be displayed in the Properties window. Again, the value is false.

Two other properties handle the validation.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsValid
{
    get { return (ValidationInstance.Count == 0); }
}

protected Validation ValidationInstance { get; set; }

In VB:

<Bindable(False)> _   
<BrowsableAttribute(False)> _
Public ReadOnly Property IsValid() As Boolean
    Get
        Return (ValidationInstance.Count = 0)
    End Get
End Property

Private _ValidationInstance As Validation
Protected Property ValidationInstance() As Validation
    Get
        Return _ValidationInstance
    End Get
    Private Set(ByVal value As Validation)
        _ValidationInstance = value
    End Set
End Property

Again, the public property is marked with the Browsable and Bindable attributes. The ValidationInstance property retains an instance of the Validation class for the business object. The code for the Validation class is here.

The constructor creates an instance of the Validation class.

In C#:

protected BoBase()

   ValidationInstance = new Validation();
}

In VB:

Protected Sub New()
    ValidationInstance = New Validation
End Sub

The following is the implementation of IDataErrorInfo.

In C#:

#region IDataErrorInfo Members

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string Error
{
    get { return ValidationInstance.ToString(); }
}

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string this[string columnName]
{
    get { return ValidationInstance[columnName]; }
}

#endregion

In VB:

#Region " Properties required by the IDataErrorInfo"

    <Bindable(False)> _   
    <BrowsableAttribute(False)> _
    Public ReadOnly Property [Error]() As String _
           Implements IDataErrorInfo.Error
        Get
            Return ValidationInstance.ToString
        End Get
    End Property

    <BrowsableAttribute(False)> _
    <Bindable(False)> _
    Default Protected ReadOnly Property Item(ByVal columnName _ 
           As String) As String _ 
           Implements IDataErrorInfo.Item
        Get
            Return ValidationInstance.Item(columnName)
        End Get
    End Property

#End Region

The Error property uses the overridden ToString method of the validation class to return the full list of validation errors.

The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.

The following in the implementation of INotifyPropertyChanged.

In C#:

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion

In VB:

#Region " Events required by INotifyPropertyChanged"
    Public Event PropertyChanged(ByVal sender As Object, _
        ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
        Implements INotifyPropertyChanged.PropertyChanged
#End Region

This interface only defines a single event. This event should be raised whenever the data is changed.

Since every business object will have unique requirements for the save operation, the SaveItem method is not implemented. Rather it is defined as abstract.

In C#:

public abstract Boolean SaveItem();

In VB:

Public MustOverride Function SaveItem() As Boolean

Defining an abstract (MustOverride in VB) SaveItem method ensures that every business object has a SaveItem method.

Finally, since the EntityState property setter is private, the base class needs a method to set the entity state.

In C#:

protected internal void SetEntityState(EntityStateType newEntityState)
{
    switch (newEntityState)
    {
        case EntityStateType.Deleted:
        case EntityStateType.Unchanged:
        case EntityStateType.Added:

            this.EntityState = newEntityState;
            break;

    default:
            if (this.EntityState == EntityStateType.Unchanged)
                this.EntityState = newEntityState;
            break;
    }
}

protected internal void SetEntityState(EntityStateType newEntityState, string propertyName)
{
    SetEntityState(newEntityState);
    if (PropertyChanged != null)
        PropertyChanged(this,
              
new PropertyChangedEventArgs(propertyName));
}

In VB:

Protected Friend Sub SetEntityState(ByVal dataState As EntityStateType)
    SetEntityState(dataState, Nothing)
End Sub

Protected Friend Sub SetEntityState( _
ByVal newEntityState As EntityStateType, ByVal propertyName As String)
    Select Case newEntityState
        Case EntityStateType.Deleted, _
             EntityStateType.Unchanged, _
             EntityStateType.Added

            Me.EntityState = newEntityState

        Case Else
            If Me.EntityState = EntityStateType.Unchanged Then
                Me.EntityState = newEntityState
            End If
    End Select

    If Not String.IsNullOrEmpty(propertyName) Then
        Dim e As New PropertyChangedEventArgs(propertyName)
        RaiseEvent PropertyChanged(Me, e)
    End If
End Sub

The SetEntityState method has two overloads. The first is used when changing the entity state in general and the second is used when changing the entity state because a specific property is changed.

For example, when setting an object as Unchanged, Added, or Deleted, it does not matter which property was changed. But when a particular property is changed, the code must also raise the PropertyChanged event.

In this case, the C# and VB code was implemented differently. In the C# code, the code to set the entity state is in the first overload. The second overload then calls the first and then raises the event.

In the VB code, the first overload simply calls the second overload. The second overload then sets the entity state and then raises the event as appropriate. You can use either technique in either language.

That’s it! You now have a base class that can be used with any business object class. If you have any other common functionality, you can add it to this base class.

Here is an example of how you use this base class.

In C#:

public class Customer : BoBase
{

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set
        {
            if (_LastName == null || _LastName != value)
            {
              string propertyName = "LastName";
              _LastName = value;

              // Validate the last name
              ValidationInstance.ValidateClear(propertyName);
              ValidationInstance.ValidateRequired(propertyName, value);

              SetEntityState(EntityStateType.Modified, propertyName);
            }
        }
    }

    public override Boolean SaveItem()
    {
        // TODO: Add code here
    }

}

In VB:

Public Class Customer
    Inherits BoBase

    Private _LastName As String
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            If _LastName Is Nothing OrElse _LastName <> value Then
              Dim propertyName As String = "LastName"
              _LastName = value

              ' Validate the last name
              ValidationInstance.ValidateClear(propertyName)
              ValidationInstance.ValidateRequired(propertyName, value)

              SetEntityState(EntityStateType.Modified, propertyName)
            End If
        End Set
    End Property

    Public Overrides Function SaveItem() As Boolean
        ' TODO: Add code here
    End Function
End Class

Notice how the class statement includes the syntax to inherit from BoBase. The LastName property uses the ValidationInstance defined in the base class to validate the value. It also sets the entity state when the last name is changed.

Enjoy!

with 16 comment(s)
Filed under: , , , , , ,

A common requirement in most applications is to validate the data entered by the user. This is such a common requirement, that it makes sense to build a reusable Validation class. This post details the beginnings of a .NET Validation class.

The class was originally designed to validate business object properties. But it could also be used to directly validate fields in your user interface if you are not yet using business objects.

In C#:

using System;
using System.Collections.Generic;
using System.Text;

namespace InStepLibrary
{
    public class Validation
    {

    }
}

In VB:

Imports System.Text

Public Class Validation

End Class

This class uses a StringBuilder, hence the need for the System.Text imports.

The Validation class has three properties.

In C#:

public int Count
{
    get
    {
        return ValidationList.Count;
    }
}

public string this[string propertyName]
{
    get
    {
        if (ValidationList.ContainsKey(propertyName))
            return ValidationList[propertyName];
        else
            return null;
    }
}

private Dictionary<String, String> ValidationList { get; set; }

In VB:

Public ReadOnly Property Count() As Integer
    Get
        Return ValidationList.Count
    End Get
End Property

Public ReadOnly Property Item(ByVal propertyName As String) As String
    Get
        If ValidationList.ContainsKey(propertyName) Then
            Return ValidationList.Item(propertyName)
        Else
            Return Nothing
        End If
    End Get
End Property

Private _ValidationList As Dictionary(Of String, String)
Private Property ValidationList() As Dictionary(Of String, String)
       Get
        Return _ValidationList
    End Get
    Set(ByVal value As Dictionary(Of String, String))
        _ValidationList = value
    End Set
End Property

The ValidationList property retains a private Dictionary of validation errors. The key of the dictionary is the property name and the value is the error text. For example, the “LastName” property may have a validation error such as “Last Name is required.”

The Count property returns the number of properties with validation errors by returning the count of the ValidationList items.

The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.

The constructor ensures that a new instance of the ValidationList Dictionary is created.

In C#:

public Validation()
{
    // Create the list to contain the validation errors
    ValidationList = new Dictionary<String, String>();
}

In VB:

Public Sub New()
    ' Create the list to contain the validation errors
    ValidationList = New Dictionary(Of String, String)
End Sub

The ToString method is overwritten in the Validation class to build a single string containing all of the validation errors.

In C#:

public override string ToString()
{
    StringBuilder sb = new StringBuilder();

    foreach (string k in ValidationList.Values)
        sb.AppendLine(k + ": " + ValidationList[k]);

    return sb.ToString();
}

In VB:

Public Overrides Function ToString() As String
    Dim sb As New StringBuilder

    For Each k As String In ValidationList.Keys
        sb.AppendLine(k & ": " & ValidationList(k))
    Next
    Return sb.ToString
End Function

This method uses the StringBuilder class to build up the potentially large string of errors.

When a validation error is added to the list, it is added for a particular property. If the property already has a validation error, additional validation errors are appended to it, separated by semi-colons (;). An AddValidationError method handles this logic.

In C#:

private void AddValidationError(string propertyName, string message)
{
    // If the property already has a message, append this message
    if (ValidationList.ContainsKey(propertyName))
    {
        string existingMessage = ValidationList[propertyName];

        if (!existingMessage.Contains(message))
            // Append the new message to the existing message
            ValidationList[propertyName] += "; " + message;
    }
    else
        // Add the message to the validation list
        ValidationList.Add(propertyName, message);
}

In VB:

Private Sub AddValidationError(ByVal propertyName As String, _
         ByVal message As String)

    ' If the property already has a message, append this message
    If ValidationList.ContainsKey(propertyName) Then
        Dim existingMessage As String = ValidationList(propertyName)

        If Not existingMessage.Contains(message) Then
            ' Append the new message to the existing message
            ValidationList(propertyName) &= "; " & message
        End If
    Else
        ' Add the message to the validation list
        ValidationList.Add(propertyName, message)
    End If
End Sub

A ValidateClear method clears any existing validation errors for a property. This method should be called before performing any new validation on the property. For example, the user leaves the Last Name field empty. The validation is performed and a validation error entry is created in the ValidationList. Then the user enters a value into the last name field. The original validation error must be cleared before revalidating the value.

In C#:

public void ValidateClear(string propertyName)
{
    // If the Property doesn't have any messages, this is done
    if (ValidationList.ContainsKey(propertyName))
        // Otherwise, remove the entry
        ValidationList.Remove(propertyName);
}

In VB:

Public Sub ValidateClear(ByVal propertyName As String)
    ' If the Property doesn't have any messages, this is done
    If ValidationList.ContainsKey(propertyName) Then
        ' Otherwise, remove the entry
        ValidationList.Remove(propertyName)
    End If
End Sub

The ValidateClear method uses the Remove method of the Dictionary to remove any Dictionary entry for the property.

That’s it for the basics of the Validation class. The only thing that is left to do is build all of the methods to perform the types of validation that your application requires.

For example, a common requirement is to ensure that a property is not left blank.

In C#:

public bool ValidateRequired(string propertyName,
                             string value)
{
    string newMessage = String.Empty;

    if (String.IsNullOrEmpty(value))
    {
        newMessage =
           String.Format("{0} is required, please enter a valid value",
                                    propertyName);
        // Add the message to the validation list
        AddValidationError(propertyName, newMessage);
        return false;
    }
    else
        return true;
}

In VB:

Public Function ValidateRequired(ByVal propertyName As String, _
                                 ByVal value As String) As Boolean
    Dim newMessage As String = String.Empty

    If String.IsNullOrEmpty(value) Then
      newMessage = _
        String.Format("{0} is required. Please enter a valid value.", _
                                  propertyName)
      ' Add the message to the validation list
      AddValidationError(propertyName, validationMessage)
      Return False
    Else
      Return True
    End If

End Function

This function performs the validation, adding a validation error as appropriate.

Another common requirement is that a property not exceed a maximum length.

In C#:

public Boolean ValidateLength(string propertyName,
                                string value, int maxLength)
{
    String newMessage = String.Empty;

    if (!String.IsNullOrEmpty(value) && value.Length > maxLength)
    {
        newMessage = String.Format("{0} has a maximum size of {1}",
                                 propertyName, maxLength);
        // Add the message to the validation list
        AddValidationError(propertyName, newMessage);
        return false;
    }
    else
        return true;
}

In VB:

Public Function ValidateLength(ByVal propertyName As String, _
         ByVal value As String, ByVal maxLength As Integer) As Boolean
    Dim sMessage As String = String.Empty

    If Not String.IsNullOrEmpty(value) AndAlso _
                             value.Length > maxLength Then
        sMessage = String.Format("{0} has a maximum size of {1}.", _
                                propertyName, maxLength)
        ' Add the message to the validation list
        AddValidationError(propertyName, sMessage)
        Return False
    Else
        Return True
    End If
End Function

You get the idea. You can add any number of these to perform whatever validation your application requires.

Some additional suggestions:

  • ValidateAlphaNumeric
  • ValidateDirectory
  • ValidateEnum
  • ValidateFileExists
  • ValidateMinLength
  • ValidateNonZero
  • ValidateNoSpaces
  • ValidateNumeric

You can create any validation method you need for your application. Just add code to perform the validation and then call AddValidationError as appropriate.

You call these validation methods from your business objects as shown below.

NOTE: This code assume you have a ValidationInstance variable that is declared in your class as a new instance of the Validation class.

In C#:

private string _LastName;
public string LastName
{
    get { return _LastName; }
    set
    {
        if (_LastName == null || _LastName != value)
        {
            string propertyName = "LastName";
            _LastName = value;

            // Validate the last name
            ValidationInstance.ValidateClear(propertyName);
            ValidationInstance.ValidateRequired(propertyName, value); 
        }
    }
}

In VB:

Private _LastName As String
Public Property LastName() As String
    Get
        Return _LastName
    End Get
    Set(ByVal value As String)
        If _LastName Is Nothing OrElse _LastName <> value Then
            Dim propertyName As String = "LastName"
            _LastName = value

            ' Validate the last name
            ValidationInstance.ValidateClear(propertyName)
            ValidationInstance.ValidateRequired(propertyName, value) 
        End If
    End Set
End Property

Or you can call these validation methods directly from your user interface as shown below.

NOTE: This code assume you have a ValidationInstance variable that is declared in your form as a new instance of the Validation class.

In C#:

private void textBox1_Validating(object sender, CancelEventArgs e)
{
    string propertyName  = "Last Name";
    validationInstance.ValidateClear(propertyName);
    validationInstance.ValidateRequired(propertyName, textBox1.Text);
    validationInstance.ValidateLength(propertyName, textBox1.Text, 20);

    if (validationInstance.Count > 0)
        errorProvider1.SetError(textBox1,
                        validationInstance[propertyName]);
    else
        // Clear the validation error
        errorProvider1.SetError(textBox1, String.Empty);
}

In VB:

Private Sub TextBox1_Validating(ByVal sender As Object, _
             ByVal e As System.ComponentModel.CancelEventArgs) _
             Handles TextBox1.Validating
    Dim propertyName As String = "Last Name"
    validationInstance.ValidateClear(propertyName)
    validationInstance.ValidateRequired(propertyName, TextBox1.Text)
    validationInstance.ValidateLength(propertyName, TextBox1.Text, 20)

    If validationInstance.Count > 0 Then
        ErrorProvider1.SetError(TextBox1, _
                        validationInstance.Item(propertyName))
    Else
        ' Clear the validation error
        ErrorProvider1.SetError(TextBox1, String.Empty)
    End If
End Sub

Hope this helps you encapsulate all of your validation logic into a reusable class.

Enjoy!

with 3 comment(s)
Filed under: , , , ,

Being a physics major in college, I am familiar with the Heisenberg Uncertainty Principle which basically states that in quantum physics, some properties (like position and momentum) cannot both be known to any precision.

Though not technically accurate, the Heisenberg Uncertainty Principle is often simplified to what is called the observer effect. This refers to the fact that the act of observing something can change its behavior.

This is very often true in programming, especially when it comes to debugging. The act of debugging an application can alter how the application behaves. Due to the comingling of the meaning of the Heisenberg Uncertainty Principle and the observer effect, the types of bugs that disappear or are altered by observing them are sometimes called heisenbugs.

Ever try debugging drag and drop operations? How about mouse movements?

Have you found any heisenbugs in your code? Want to share them? Post your thoughts using the comments.

Thanks!

with 1 comment(s)
Filed under: , , , ,

My prior post demonstrated how to bind to a list of month names. Once the user picks the desired month, you may want to provide a list of valid dates to pick a date in that month. For example: 1-30 for September and 1-31 for July.

You can accomplish this using a switch statement (Select Case in VB), but that hard-codes the dates, does not handle leap year, and does not support localization.

Another option is to use the culture specific calendar as shown below.

The code is first shown in both VB and C#. It is then described in detail below.

In C#:

private List<int> GetListOfDays(int yearNumber , int monthNumber)
{
    Calendar currentCalendar  = CultureInfo.CurrentCulture.Calendar;
    int numberOfDays  =
              currentCalendar.GetDaysInMonth(yearNumber, monthNumber);
    return Enumerable.Range(1, numberOfDays).ToList();
}

In VB:

Private Function GetListOfDays(ByVal yearNumber As Integer, _
                   ByVal monthNumber As Integer) As List(Of Integer)
  Dim currentCalendar As Calendar = CultureInfo.CurrentCulture.Calendar
  Dim numberOfDays As Integer = _
               currentCalendar.GetDaysInMonth(yearNumber, monthNumber)
  Return Enumerable.Range(1, numberOfDays).ToList()
End Function

This function first defines the calendar based on the current culture. It then uses the calendar’s GetDaysInMonth method to get the appropriate number of days in the month.

It then uses the Range method of Enumerable to build a list of numbers from 1 to the number of days in the month and returns it as a list of integers.

This method is called as follows:

In C#:

comboBox1.DataSource = GetListOfDays(2009, 2);

In VB:

ComboBox1.DataSource = GetListOfDays(2009, 2)

Enjoy!

with 1 comment(s)
Filed under: , , , , ,

Visual Studio comes with DateTimePicker and MonthCalendar controls that provide a standard looking calendar for the user to pick a date. But there are times when these controls don’t provide the features you need.

For example, say you want to ask the user for the month and year that their credit card expires. Or you want to ask for a birth date and don’t want the user to have to scroll back to 1932. Or better yet, don’t need to know the year (not that I refuse to admit my birth year <G>.)

There is an easy way to achieve this.

The code is first shown in both VB and C#. It is then described in detail below.

In C#:

string[] MonthNames=
               CultureInfo.CurrentCulture.DateTimeFormat.MonthNames;
comboBox1.DataSource = MonthNames;

In VB:

Dim MonthNames As String() =  _
               CultureInfo.CurrentCulture.DateTimeFormat.MonthNames
ComboBox1.DataSource = MonthNames

This code gets the month names based on the current culture. This ensures that you get the correct localized names. You can then bind the array to a ComboBox or ListBox.

NOTE: In both cases, be sure to set a reference and import System.Globalization.

When looking at the list from the US (and most countries), there is a problem. There appears to be room for 13 values in the list. This is for those countries that have 13 months, such as Ethiopia.

So for most countries, there is a blank entry at the bottom of the list when it is bound. There are numerous ways of removing the empty entry. Even though it might not be the most performant, I like lambda expressions, so here is one way to remove the empty entry.

In C#:

comboBox1.DataSource = 
     MonthNames.Where(m => !string.IsNullOrEmpty(m)).ToList();

In VB:

ComboBox1.DataSource = _
    MonthNames.Where(Function(m) Not String.IsNullOrEmpty(m)).ToList()

Enjoy!

with 1 comment(s)
Filed under: , , , , , ,

The Enum keyword allows you to define a standard set of named constants for use in your application. Sometimes you may want to present this same list of  values to your user.

You can display the set of Enum values in a ComboBox or ListBox using data binding. And even better, if you use the DescriptionAttribute in the Enum, you can display a more user-friendly set of values.

For example, a Customer business object may have a CustomerType defined with a specific set of options for corporations, individuals, schools, and charities. You want to use these values in your code, but also display them to the user in a ComboBox.

Enum Definition

The Enum can reside in the Customer class, or in its own class.

In C#:

public enum CustomerTypeOption
{
    NotDefined, 
    Corp, 
    IndividualorFamily, 
    SchoolorUniversity, 
    Charity
}

In VB:

Public Enum CustomerTypeOption
   NotDefined
   Corp
   IndividualorFamily
   SchoolorUniversity
   Charity
End Enum

CustomerType Property

A CustomerType property in the Customer class can use this enum type.

In C#:

public CustomerTypeOption CustomerType { get; set; }

In VB:

Private _CustomerType As CustomerTypeOption
Public Property CustomerType() As CustomerTypeOption
    Get
        Return _CustomerType
    End Get
    Set(ByVal value As CustomerTypeOption)
        _CustomerType = value
    End Set
End Property

The C# code uses the automatically implemented properties feature introduced with .NET 3.5. The VB code uses the full property definition.

Binding To an Enum

The Enum can also be used by the user interface. For example, you can bind a comboBox or listBox to a set of customer type options.

In C#:

Dictionary<string, int> CustomerTypeList =
                           new Dictionary<string, int>();

foreach (int enumValue in
             Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    CustomerTypeList.Add(
        
Enum.GetName(typeof(Customer.CustomerTypeOption),
                                      enumValue ), enumValue);
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
                                                                null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

For Each enumValue As Integer In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    CustomerTypeList.Add( _
       [Enum].GetName(GetType(Customer.CustomerTypeOption), _
                                        enumValue), enumValue)
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                               Nothing)

This code defines a Dictionary to contain the name of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values and adds the name and value to the dictionary.

The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.

The result looks like this:

image

While it does provide the appropriate options, it is not very user-friendly. It would be nicer if there were appropriate spaces and full words. For that, you can use the DescriptionAttribute on the Enum.

Using the DescriptionAttribute

NOTE: Be sure to import the System.ComponentModel and the System.Reflection namespaces.

In C#:

public enum CustomerTypeOption
{
    [DescriptionAttribute("Not Specified")]
    NotDefined,

    [DescriptionAttribute("Corporation")]
    Corp,

    [DescriptionAttribute("Individual or Family")]
    IndividualorFamily,

    [DescriptionAttribute("School or University")]
    SchoolorUniversity,

    [DescriptionAttribute("Charity")]
    Charity
}

In VB:

Public Enum CustomerType
    <DescriptionAttribute("Corporate Customer")> _
    CorpCustomer

    <DescriptionAttribute("Individual or Family")> _
    IndividualorFamily

    <DescriptionAttribute("School or University")> _
    SchoolorUniversity

    <DescriptionAttribute("Charity")> _
    Charity

    <DescriptionAttribute("Kingdom")> _
    Kingdom
End Enum

Binding to an Enum Description

Now you can bind to the description instead of to the Enum constant.

In C#:

Dictionary<string, int> CustomerTypeList =
                                new Dictionary<string, int>();

FieldInfo fi;
DescriptionAttribute da;
foreach (Customer.CustomerTypeOption enumValue in
           Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    fi = typeof(Customer.CustomerTypeOption).
                    GetField((enumValue.ToString()));
    da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi,
                    typeof(DescriptionAttribute));
    if (da != null)
    {
       CustomerTypeList.Add(da.Description, (int)enumValue);
    }
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList,
                                                              null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

Dim fi As FieldInfo
Dim da As DescriptionAttribute
For Each enumValue As Customer.CustomerTypeOption In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    fi = GetType(Customer.CustomerTypeOption). _
                GetField(enumValue.ToString)
    da = DirectCast(Attribute.GetCustomAttribute(fi, _
                GetType(DescriptionAttribute)), DescriptionAttribute)
    If da IsNot Nothing Then
        CustomerTypeList.Add(da.Description, enumValue)
    End If
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                              Nothing)

This code defines a Dictionary to contain the description of the enum and the integer value of the enum. The for/each loop iterates through the set of Enum values. It uses reflection to find the DescriptionAttribute. It then adds the description and value to the dictionary.

The code then binds to the ComboBox, setting the DisplayMember to the “Key” of the dictionary and the ValueMember to the “Value” of the Dictionary.

Now the combo box looks like this:

image

Much better!

Extension Methods

So the user interface looks nicer, but the code to get the DescriptionAttribute looks rather tedious. It would be much nicer if there was a GetDescription method on the Enum. But hey, with the extension method feature introduced in .NET 3.5, we can build one ourselves!

In C#:

public static string GetDescription(this Enum currentEnum)
{
    string description = String.Empty;
    DescriptionAttribute da ;

    FieldInfo fi = currentEnum.GetType().
                GetField(currentEnum.ToString());
    da = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, 
               
typeof(DescriptionAttribute));
    if (da != null)
        description = da.Description;
    else
        description = currentEnum.ToString();

    return description;
}

In VB:

<ExtensionAttribute()> _
Public Function GetDescription(ByVal currentEnum As [Enum]) As String
    Dim description As String = String.Empty
    Dim da As DescriptionAttribute

    Dim fi As FieldInfo = currentEnum.GetType. _
                GetField(currentEnum.ToString)
    da = DirectCast(Attribute.GetCustomAttribute(fi, _
                GetType(DescriptionAttribute)), DescriptionAttribute)
    If da IsNot Nothing Then
        description = da.Description
    Else
        description = currentEnum.ToString
    End If

    Return description
End Function

The C# code can reside in any static class. The VB code must reside in a module, which provides the same features as a static class.

This code uses reflection to get the DescriptionAttribute for any Enum value. So it can be readily reused. The code returns the DescriptionAttribute if one is found, otherwise it returns the name.

Binding to an Enum Description - Redux

Now the binding code is much easier to work with.

In C#:

Dictionary<string, int> CustomerTypeList =
                         new Dictionary<string, int>();

foreach (Customer.CustomerTypeOption enumValue in
           Enum.GetValues(typeof(Customer.CustomerTypeOption)))
{
    CustomerTypeList.Add(enumValue.GetDescription(), (int)enumValue);
}

// Bind the customer type combo box
CustomerTypeComboBox.DisplayMember = "Key";
CustomerTypeComboBox.ValueMember = "Value";
CustomerTypeComboBox.DataSource = new BindingSource(CustomerTypeList, 
                                                              null);

In VB:

Dim CustomerTypeList As New Dictionary(Of String, Integer)

For Each enumValue As Customer.CustomerTypeOption In _
            [Enum].GetValues(GetType(Customer.CustomerTypeOption))
    CustomerTypeList.Add(enumValue.GetDescription, enumValue)
Next

' Bind the combo box
CustomerTypeComboBox.DisplayMember = "Key"
CustomerTypeComboBox.ValueMember = "Value"
CustomerTypeComboBox.DataSource = New BindingSource(CustomerTypeList, _
                                                              Nothing)

Enjoy!

Most business applications have business objects such as customer, order, or invoice. Often, the data access layer (DAL) provides the data and your code needs to use that data to manually populate a business object.

This post describes how to manually populate a business object from a DataTable. It uses the Customer class defined here.

The code is first shown in both VB and C#. It is then described in detail below.

In C#:

public static List<Customer> Retrieve()
{
   DataTable dt = Dac.ExecuteDataTable("CustomerRetrieveAll", null);

    List<Customer> customerList = new List<Customer>();

    foreach (DataRow dr in dt.Rows)
    {
        customerList.Add(new Customer()
                    {
                        CustomerId = (int)dr["CustomerId"],
                        LastName = (string)dr["LastName"],
                        FirstName = (string)dr["FirstName"]
                    });
    }

    return customerList;
}

In VB:

Public Shared Function Retrieve() As List(Of Customer)

    Dim dt As DataTable = Dac.ExecuteDataTable( _
                   
"CustomerRetrieveAll", nothing)

    Dim customerList As New List(Of Customer)

    For Each dr As DataRow In dt.Rows
       customerList.Add(New Customer With _
                    {.CustomerId = CType(dr("CustomerID"), Integer), _
                     .LastName = dr("LastName").ToString, _
                     .FirstName = dr("FirstName").ToString}) 
    Next

    Return customerList
End Function

The Retrieve method is public and static (shared in VB). It is public so it can be called from the user interface code. It is static/shared because it does not use or retain any state. This allows the function to be called without creating an instance of the class containing the function.

This function calls the ExecuteDataTable method from here, which returns a DataTable. It then creates a new list and adds a new customer to the list for each row in the DataTable.

The resulting list of customers is returned. The user interface code can then use this list to bind to a control such as a grid or combo box. You can also search, sort, filter, or work with this list using LINQ or Lambda expressions.

NOTE: If the fields in the DataTable match the properties of the business object, you could use reflection to map the fields to the properties. Though reflection does have a performance hit, it provides a more general solution that could be used by any business object.

Enjoy!

with 1 comment(s)
Filed under: , , , , , ,
More Posts Next page »