September 2009 - Posts
The number of lines of code is not a very useful metric for evaluating good code or a good developer. However, it is sometimes useful to know how much code you have.
For example, I recently worked on a project converting a VB 6 application to .NET. When we were finished, it was interesting to see how we had reduced the number of lines of code by 50% and yet added functionality.
There are tools available to help you get counts. But if you just want a quick and dirty technique, here is some code.
NOTE: Be sure to import System.IO.
In C#:
public class LineCount
{
private Dictionary<string, int> _FilesInProject =
new Dictionary<string, int>();
public Dictionary<string, int> FilesInProject
{
get { return _FilesInProject; }
}
public int GetLineCount(string directoryName,
string[] fileExtensions)
{
int LineCount = 0;
int TotalLineCount = 0;
DirectoryInfo topDirectoryInfo =
new DirectoryInfo(directoryName);
// Process the directory and all subdirectories
foreach (DirectoryInfo dir in
topDirectoryInfo.GetDirectories())
{
// Loop the file types
foreach (string fileExtension in fileExtensions)
{
foreach (FileInfo file in dir.GetFiles(fileExtension))
{
// open files for streamreader
StreamReader sr = new StreamReader(file.FullName);
//loop until the end
while ((sr.ReadLine() != null))
{
LineCount += 1;
}
//close the streamreader
sr.Close();
// add the file name to the list
FilesInProject.Add(file.FullName, LineCount);
// Handle the line counting
TotalLineCount += LineCount;
LineCount = 0;
}
}
}
return TotalLineCount;
}
}
In VB:
Public Class LineCount
Private _FilesInProject As New Dictionary(Of String, Integer)
Public ReadOnly Property FilesInProject() _
As Dictionary(Of String, Integer)
Get
Return _FilesInProject
End Get
End Property
Public Function GetLineCount(ByVal directoryName As String, _
ByVal fileExtensions() As String) As Integer
Dim LineCount As Integer = 0
Dim TotalLineCount As Integer = 0
Dim topDirectoryInfo As New DirectoryInfo(directoryName)
' Process the directory and all subdirectories
For Each dir As DirectoryInfo In _
topDirectoryInfo.GetDirectories
' Loop the file types
For Each fileExtension As String In fileExtensions
For Each file As FileInfo In _
dir.GetFiles(fileExtension)
' open files for streamreader
Dim sr As New StreamReader(file.FullName)
'loop until the end
While Not (sr.ReadLine() Is Nothing)
LineCount += 1
End While
'close the streamreader
sr.Close()
' add the file name to the list
FilesInProject.Add(file.FullName, LineCount)
' Handle the line counting
TotalLineCount += LineCount
LineCount = 0
Next
Next
Next
Return TotalLineCount
End Function
End Class
This code counts the number of lines in each file and retains a total count. It stores the names of the files and the line count for each file in a dictionary. It returns the total count as the return value from the function. If you only need the total count, you can remove the Dictionary.
The LineCount class has one read-only property (FilesInProject) that defines the files within a specified directory along with each files line count. The file name is stored as the key and the file count as the value in each dictionary entry.
The GetLineCount method takes a directory name and a set of file extensions. All files in all directories and subdirectories with any of the defined extensions will be found and counted.
The code uses the DirectoryInfo class to loop through the directories. It then uses the FileInfo class to get the files with the defined extensions.
For each found file, it uses a StreamReader to read the file and count the lines.
You can make this method fancier by adding additional code before counting the line. For example, you could skip blank lines, or lines with comments, or lines with just { or }. This simple case just counts all of the lines.
The code stores each found file along with its line count into the dictionary. It also accumulates the total line count.
When it is finished, it returns the total line count.
You can use this class like this:
In C#:
string[] fileExtensions = {"*.cs"};
string directoryName = @"C:\Tools\SampleApplication";
LineCount lc = new LineCount();
int totalLines =
lc.GetLineCount(directoryName, fileExtensions);
foreach (KeyValuePair<string,int> f in lc.FilesInProject)
Debug.WriteLine(f.Key + ": " + f.Value);
Debug.WriteLine("Total: " + totalLines);
In VB:
Dim fileExtensions() As String = {"*.vb"}
Dim directoryName As String = "C:\Tools\SampleApplication"
Dim lc As New LineCount
Dim totalLines As Integer = _
lc.GetLineCount(directoryName, fileExtensions)
For Each f In lc.FilesInProject
Debug.WriteLine(f.Key & ": " & f.Value)
Next
Debug.WriteLine("Total: " & totalLines)
This code sets up an array of file extensions. In this example, only one extension is included in the array. The code then sets up the directory to search.
The code then calls the GetLineCount and then loops through the resulting Dictionary to list the found files and their line counts.
Enjoy!
Whether it be SQL Server, Access, Oracle, or mySql, most applications write to one kind of database. But what if your application requirements are such that you have to support multiple database types? Then the DbProviderFactory is for you.
For example, say you are building a product to sell to HR departments. Some of your customers want to leverage their existing SQL Server infrastructure and will only buy the product if it supports SQL Server. Other customers want your application to work in an all Oracle environment, still others would prefer mySql. So what do you do? Build multiple versions of the product? No need!
Part of ADO.NET, the DbProviderFactory allows you to have one set of code that works with any type of database that supports a data provider such as ODBC, OleDb, or SQL Server.
The code below takes a stored procedure and a set of parameters and returns a DataTable. You could easily adjust this code to take a sql string instead of a stored procedure or to return a DataReader instead of a DataTable.
NOTE: This post assumes you already have a stored procedure defined to select the required data from your database. For more information on stored procedures, see this link.
The code is first shown in both VB and C#. It is then described in detail below.
NOTE: Be sure to import the System.Data, System.Data.Common and System.Collections.Generic namespaces.
NOTE: If you plan to cut and paste this code, be sure to define two application settings: ARConnectionString to specify a connection string appropriate for your database and ARProviderName to specify an appropriate provider name for your database (such as System.Data.SqlClient or System.Data.OleDb)
In C#:
public static class Dac
{
public static DataTable ExecuteDataTable(string storedProcedureName,
params SqlParameter[] arrParam)
{
DataTable dt = new DataTable();
// Get the data factory
DbProviderFactory df = DbProviderFactories.
GetFactory(Properties.Settings.Default.ARProviderName);
// Open the connection
using (DbConnection cnn = df.CreateConnection())
{
cnn.ConnectionString =
Properties.Settings.Default.ARConnectionString;
cnn.Open();
// Define the command
using (DbCommand cmd = cnn.CreateCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = storedProcedureName;
// Handle the parameters
if (paramDictionary != null)
{
DbParameter param;
foreach (string key in paramDictionary.Keys)
{
param = cmd.CreateParameter();
// The parameter name depends on the provider
switch (df.GetType().Name)
{
case "SqlClientFactory":
param.ParameterName = "@" + key;
break;
case "OracleClientFactory":
param.ParameterName = ":" + key;
break;
case "OleDbFactory":
case "OdbcFactory":
param.ParameterName = "?";
break;
default:
break;
}
param.Value = paramDictionary[key];
cmd.Parameters.Add(param);
}
}
// Define the data adapter and fill the dataset
using (DbDataAdapter da = df.CreateDataAdapter())
{
da.SelectCommand = cmd;
da.Fill(dt);
}
}
}
return dt;
}
}
In VB:
Public Class Dac
Public Shared Function ExecuteDataTable( _
ByVal storedProcedureName As String, _
ByVal paramDictionary As Dictionary(Of String, Object)) _
As DataTable
Dim dt As New DataTable
' Get the data factory
Dim df As DbProviderFactory = DbProviderFactories. _
GetFactory(My.Settings.ARProviderName)
' Open the connection
Using cnn As DbConnection = df.CreateConnection
cnn.ConnectionString = My.Settings.ARConnectionString
cnn.Open()
' Define the command
Using cmd As DbCommand = cnn.CreateCommand
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = storedProcedureName
' Handle the parameters
If paramDictionary IsNot Nothing Then
Dim param As DbParameter
For Each key As String In paramDictionary.Keys
param = cmd.CreateParameter()
' The parameter name depends on the provider
Select Case df.GetType.Name
Case "SqlClientFactory"
param.ParameterName = "@" & key
Case "OracleClientFactory"
param.ParameterName = ":" & key
Case "OleDbFactory", "OdbcFactory"
param.ParameterName = "?"
End Select
param.Value = paramDictionary(key)
cmd.Parameters.Add(param)
Next
End If
' Define the data adapter and fill the dataset
Using da As DbDataAdapter = df.CreateDataAdapter
da.SelectCommand = cmd
da.Fill(dt)
End Using
End Using
End Using
Return dt
End Function
End Class
The ExecuteDataTable function is in my Dac (data access component) class and defined to be public and static (shared in VB). It is public so it can be called from any other code. It is static/shared because it does not use or retain any state. That allows the function to be called without creating a instance of the class containing the function.
The function has two parameters. The first is the name of a stored procedure. Since this code returns a DataTable, it assumes that the stored procedure contains one Select statement. Here is an example of a SQL Server stored procedure:
CREATE PROCEDURE dbo.CustomerRetrieveAll
AS
SELECT
CustomerId,
LastName,
FirstName
FROM
Customer
ORDER BY
LastName + ', ' + FirstName
NOTE: The structure and syntax of stored procedures in other database platforms may differ.
The second parameter is a Dictionary that defines any parameters required by the stored procedure. The dictionary key is the name of the parameter and the value is the value of the parameter. In the CustomerRetrieveAll example, there are no parameters. But your stored procedure may have parameters to filter the data.
The first line of code defines a variable for the DataTable. It is used as the return value from the function.
The next statement sets up the data provider factory. The GetFactory method returns an appropriate strongly typed data factory based on a string value representing the provider name. For SQL Server, this value is "System.Data.SqlClient", for an OleDb database, it is "System.Data.OleDb". Once you create a data factory, you can use its methods to define other data access objects.
In this example, the name of the provider is stored in a configuration file using the Settings feature in Visual Studio. (See this for information on settings in C# and this for information on settings in VB.) This allows you to change the configuration file to change the database provider used by the application. You will need to replace the property setting with a setting or provider name appropriate for your database.
You can then use the data factory to create a connection object of the appropriate type as shown in the first Using statement.
The code then defines the connection string. In this example, the connection string is also stored in the configuration file using the Settings feature in Visual Studio. You will need to replace the connection string in the example with a setting or connection string appropriate for your database.
The connection is then opened.The connection is automatically closed at the end of the Using block.
The second Using statement creates the command. In this case, the command is a stored procedure and the CommandText property of the command defines the stored procedure name.
If parameters were passed in, the parameters are added to the command’s Parameters. The format of name of the parameter is dependent on the provider, so a switch/select statement is used to set the appropriate name.
The third Using statement defines a DataAdapter, again using the data factory. The Fill method of the DataAdapter fills the DataTable with the data returned from the stored procedure.
Since the code uses the Using statement, all of the objects are appropriately closed and disposed.
The resulting filled DataTable is then returned.
As an example, this is how you call this method.
In C#:
DataTable dt = Dac.ExecuteDataTable("CustomerRetrieveAll",
null);
In VB:
Dim dt As DataTable = Dac.ExecuteDataTable("CustomerRetrieveAll", _
nothing)
If the stored procedure had parameters, you would call it like this:
In C#:
Dictionary<string, object> paramDictionary =
new Dictionary<string, object>();
paramDictionary.Add("CustomerId", custId);
DataTable dt = Dac.ExecuteDataTable("CustomerRetrieveById",
paramDictionary);
In VB:
Dim paramDictionary As New Dictionary(Of String, Object)
paramDictionary.Add("CustomerId", custId)
Dim dt As DataTable = Dac.ExecuteDataTable("CustomerRetrieveById", _
paramDictionary)
EDITED 9/28/09:
The code did not handle DbParameters correctly in the first draft of this post. This error was corrected in both the ExecuteDataTable method and in the calling examples.
Enjoy!
Though this is not necessarily a common requirement, this post demonstrates the following techniques.
- Converting a text string to an array.
- Passing data from one form to another on a constructor.
- Picking a unique set of random numbers.
- Using the set of random numbers to pick a set of items from an array.
- Building controls at runtime.
- Leveraging the FlowLayoutPanel to simplify control layout at runtime.
- Using the ErrorProvider to show end-user data entry errors.
One of the common ways to memorize a set of text is to read it several times, then blank out some of the words and attempt to fill them in. Then blank out more words and fill them in again. Repeat this process until you can fill in the entire set of text with no words.
Going to a parochial school, my daughters had to memorize lots and lots and lots of Bible verses. Some of their teachers helped them learn the verses using this technique.
Here is the basic use case/user story:
- User enters the set of text to memorize and the number of words to remove on one form.
- User identifies when this process is finished.
- The form closes and a second form appears with the defined number of words removed.
- The user enters the text for the missing words.
- The user selects to check their work. Errors are indicated.
- The user can select to try again.
- Each time, more words are removed.
In this example, the first form looks like this:
The first TextBox is set as follows:
- (Name): MemorizeTextBox
- Anchor: Top, Left, Right
- Multiline: True
The second TextBox is set as follows:
The button is set as follows:
The second form looks like this:
The first control is a FlowLayoutPanel set as follows:
- (Name): TextPanel
- Anchor: Top, Botton, Left, Right
- AutoSize: True
The Try Again button is set as follows:
- (Name): TryAgainButton
- Anchor: Bottom, Right
- Text: Try Again
The Check button is set as follows:
- (Name): CheckButton
- Anchor: Bottom, Right
- Text: Check
Also add an ErrorProvider (it shows up in the form's tray) as follows:
- (Name): ep
- BlinkStyle: NeverBlink
Let's start by coding the second form first.
PracticeWin Form
In the PracticeWin form, insert the following code. This code is described in detail below.
In C#:
public partial class PracticeWin : Form
{
// Memorized text in an array
string[] memorizeText;
// Number of blanks
int blankCount;
// Dictionary of blank textBoxes
Dictionary<int,TextBox> blanks;
// Random instance
Random rnd = new Random();
public PracticeWin(string[] textToMemorize, int intialBlankCount)
{
InitializeComponent();
this.memorizeText = textToMemorize;
this.blankCount = intialBlankCount;
}
private void PracticeWin_Load(object sender, EventArgs e)
{
// Set panel properties
TextPanel.WrapContents = true;
// set up the form
SetupForm();
}
private void SetupForm()
{
// Clear the current controls
TextPanel.Controls.Clear();
// Get a set or random numbers
blanks = new Dictionary<int, TextBox>();
int numberOfBlanks = 0;
int randomNumber;
do
{
randomNumber = rnd.Next(0, memorizeText.Length);
// Ensure this random number was not already selected
if (!blanks.ContainsKey(randomNumber))
{
blanks.Add(randomNumber, null);
numberOfBlanks += 1;
}
} while (numberOfBlanks < blankCount);
// Create a cell for each array entry
TextBox tb;
for (int i = 0; i < memorizeText.Length; i++)
{
tb = new TextBox();
tb.Margin = new Padding(tb.Margin.Left,
tb.Margin.Top,
15,
tb.Margin.Bottom);
if (blanks.ContainsKey(i))
{
tb.Text = string.Empty;
blanks[i] = tb;
}
else
{
tb.Text = memorizeText[i];
tb.ReadOnly = true;
}
TextPanel.Controls.Add(tb);
}
}
private void CheckButton_Click(object sender, EventArgs e)
{
// Check the answers
foreach (var blank in blanks)
{
if (blank.Value.Text.ToLower() ==
memorizeText[blank.Key].ToLower())
{
// Correct, so do whatever here
ep.SetError(blank.Value,string.Empty);
}
else
{
// Wrong, so do whatever here
ep.SetError(blank.Value, memorizeText[blank.Key]);
}
}
}
private void TryAgainButton_Click(object sender, EventArgs e)
{
// Increase the blank count
blankCount += 3;
if (blankCount > memorizeText.Length)
blankCount = memorizeText.Length;
// Repeat
SetupForm();
}
}
In VB:
Public Class PracticeWin
' Memorized text in an array
Dim memorizeText() As String
' Number of blanks
Dim blankCount As Integer
' Dictionary of blank textBoxes
Dim blanks As Dictionary(Of Integer, TextBox)
' Random instance
Dim rnd As New Random()
Public Sub New(ByVal textToMemorize() As String, _
ByVal intialBlankCount As Integer)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.memorizeText = textToMemorize
Me.blankCount = intialBlankCount
End Sub
Private Sub PracticeWin_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
' Set panel properties
TextPanel.WrapContents = True
' set up the form
SetupForm()
End Sub
Private Sub SetupForm()
' Clear the current controls
TextPanel.Controls.Clear()
' Get a set or random numbers
blanks = New Dictionary(Of Integer, TextBox)
Dim numberOfBlanks As Integer = 0
Dim randomNumber As Integer
Do
randomNumber = rnd.Next(0, memorizeText.Length)
' Ensure this random number was not already selected
If Not blanks.ContainsKey(randomNumber) Then
blanks.Add(randomNumber, Nothing)
numberOfBlanks += 1
End If
Loop While (numberOfBlanks < blankCount)
' Create a cell for each array entry
Dim tb As TextBox
For i As Integer = 0 To memorizeText.Length - 1
tb = New TextBox()
tb.Margin = New Padding(tb.Margin.Left, _
tb.Margin.Top, _
15, _
tb.Margin.Bottom)
If (blanks.ContainsKey(i)) Then
tb.Text = String.Empty
blanks(i) = tb
Else
tb.Text = memorizeText(i)
tb.ReadOnly = True
End If
TextPanel.Controls.Add(tb)
Next
End Sub
Private Sub CheckButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles CheckButton.Click
' Check the answers
For Each blank In blanks
If (blank.Value.Text.ToLower() = _
memorizeText(blank.Key).ToLower()) Then
' Correct, so do whatever here
ep.SetError(blank.Value, String.Empty)
Else
' Wrong, so do whatever here
ep.SetError(blank.Value, memorizeText(blank.Key))
End If
Next
End Sub
Private Sub TryAgainButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles TryAgainButton.Click
' Increase the blank count
blankCount += 3
If (blankCount > memorizeText.Length) Then
blankCount = memorizeText.Length
End If
' Repeat
SetupForm()
End Sub
End Class
Wow! That is a lot of code. But I thought it would be easier for cutting and pasting to put all of the code in and then explain it instead of having multiple pieces of code for you to paste in with lots of words around it.
Declaring the Form-Level Variables
The memorizedText array contains the array of words in the order they appear in the text to memorize. For example, if the text to memorize is this:
"That that is, is. That that is not, is not. Is that it? It is."
The memorizedText array consists of 15 entries, each containing a word from the text:
- That
- that
- is,
- is.
- …
- it?
- It
- is.
Notice that the array elements include the capitalization and punctuation. For my kids, having the correct capitalization and punctuation in the memorized text was required. If that is not the case for you, you will need to add code to remove the punctuation and capitalization.
The blankCount integer variable defines how many blanks should be inserted into the text. The blankCount increases on each try.
The blanks generic Dictionary contains the list of random numbers and their associated TextBoxes on the form. This is needed for checking the user-entered values.
The rnd variable is an instance of the Random class. It is defined here and not within the code that sets up the form to ensure that the application does not pick the same set of words to remove each time the user retries.
Defining the Constructor
The next step is to define a constructor. This constructor receives the memorized text array and the blank count from the calling form.
If you are not familiar with this technique for passing data from one form to another using a constructor, see this blog post for more information.
Loading the Form
The code in the load event of the PracticeWin form ensures that the FlowLayoutPanel wraps its contents and then calls a method to set up the form.
Setup the Form
The SetupForm routine takes advantage of the features of the FlowLayoutPanel to layout TextBoxes for each word in the memorized text. Normal words are displayed in a read-only TextBox. Words that were removed are shown with empty TextBoxes.
The code starts by clearing the FlowLayoutPanel from any prior try. It then initializes the variables for this setup. The blanks generic Dictionary holds a selected set of random numbers along with their associated TextBox. The numberOfBlanks variable counts the number of blanks that were defines. This ensures that the correct number of words were blanked out.
The Next method of the Random class is used within a loop to generate a set of random numbers. These are added to the Dictionary. If you are not familiar with this technique of generating random numbers, you can see more information in this post.
Once the correct number of random numbers is selected, the code builds the appropriate Textboxes in the FlowLayoutPanel.
The code loops for each element in the array of words. Within the For loop, the code creates a new TextBox and sets it margin. This is required to leave room for display of the ErrorProvider control.
If the list of random numbers contains the index of the current array element, this word is randomly selected to be removed. So the Text property of the TextBox is blank and the TextBox is added to the Dictionary. This helps keep track of the TextBoxes associated with the removed entires.
If the index of the current array element is not on the list of selected random numbers, the Text property is set to the array element text and the Textbox is marked as ReadOnly. This ensures that the user only enters values for the blank spaces.
The resulting Textbox is then added to the FlowLayoutPanel. The result of this process appears as follows at runtime:
Check Processing
The code behind the Check button loops through the generic Dictionary of blanks. The Key of this dictionary is the set of selected random numbers. Each random number defines the index into the word array that contains a blank. The Value of the blanks Dictionary contains the TextBox associated with the blank.
So the code can retrieve the user-entered value by using blank.Value.Text. This value can be compared to the value in the original word array using the key as the index into the memorizedText array.
If the values are equal, any errors are cleared. If the value is not equal, the ErrorProvider SetError method is used to display an error icon.
The result at run-time will look something like this:
Notice the red ErrorProvider icons. If the user hovers over the icon, the correct answer is displayed.
Try Again Processing
The TryAgainButton click event increases the blankCount by 3. You can change this to any value. It then ensures it does not calculate a blankCount greater than the number of words and calls SetupForm to reset up the form for another try.
MemorizeWin Form
In the MemorizeWin form, insert the following code. This code is described in detail below.
In C#:
public partial class MemorizeWin : Form
{
public MemorizeWin()
{
InitializeComponent();
}
private void PracticeButton_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(MemorizeTextBox.Text) )
{
MessageBox.Show("Enter the text to memorize");
return;
}
// Put the text into an array
string[] wordArray = MemorizeTextBox.Text.Split(' ');
int blanks = 0;
if (int.TryParse(BlanksTextBox.Text, out blanks))
{
if (blanks > wordArray.Length)
{
MessageBox.Show(
"The number of blanks cannot exceed the number of words");
}
else
{
PracticeWin frmPractice = new PracticeWin(wordArray,
blanks);
this.Hide();
frmPractice.ShowDialog();
this.Show();
}
}
else
{
MessageBox.Show("The number of blanks must be numeric");
}
}
}
In VB:
Public Class MemorizeWin
Private Sub PracticeButton_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles PracticeButton.Click
If String.IsNullOrEmpty(MemorizeTextBox.Text) Then
MessageBox.Show("Enter the text to memorize")
Return
End If
' Put the text into an array
Dim memorizedTextArr() As String = _
MemorizeTextBox.Text.Split(" "c)
Dim blanks As Integer = 0
If Integer.TryParse(BlanksTextBox.Text, blanks) Then
If (blanks > memorizedTextArr.Length) Then
MessageBox.Show( _
"The number of blanks cannot exceed the number of words")
Else
Dim frmPractice As New PracticeWin(memorizedTextArr, _
blanks)
Me.Hide()
frmPractice.ShowDialog()
Me.Show()
End If
Else
MessageBox.Show("The number of blanks must be numeric")
End If
End Sub
End Class
This code performs some basic checking to ensure that valid values were entered. It also converts the entered text into an array. If you are not familiar with this technique of using Split to convert a string to an array, see this post.
The code then calls the constructor for PracticeWin, passing in the array of words and the number of blanks. If you are not familiar with this technique of passing values from one form to another using a constructor, see this post.
When the user is finished practicing, the first form re-appears. The user can click Practice again to practice the same text again or can enter another set of text to memorize.
Though you may never need this exact application, the code for this application demonstrated quite a few useful techniques.
Enjoy!
It is often necessary for one form in your WinForms application to pass information to another form in your application. Though there are multiple ways to pass the data, this post focuses on using a form constructor.
Take a simple case, on Form1 there is a TextBox1 where the user enters data and clicks Button1. When the button is clicked, Form2 is displayed. Form2 needs the data that was entered on Form1.
To try this:
1. Create a form. Form1 needs a TextBox and button and described above.
2. Create a second form. You can add a label or other textbox if you want to confirm that the data was passed to this form.
3. Open the code editor for Form2.
4. Insert the following code.
In C#:
public partial class Form2: Form
{
// Variable to store the passed in text
string passedInText;
public Form2(string text)
{
InitializeComponent();
this.passedInText= text;
}
}
In VB:
Public Class Form2
' Variable to store the passed in text
Dim passedInText As String
Public Sub New(ByVal text As String)
' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.passedInText= text
End Sub
End Class
This code defines a variable to store the passed in text (passedInText).
The text is passed in on the form's constructor. In C#, the constructor is named using the name of the class, Form2 in this case. In VB, the constructor is defined using a subroutine named New. In either case, the parameter defines the text that is passed in.
Within the constructor, the code assigns the passed in value to the defined variable. So passedInText then refers to the text that was passed in to this form.
You can use the passedInText variable to assign the passed in text to a TextBox or Label on Form2 if you want to see the result. Otherwise, you can view the passedInText using the debugger or Output window.
So now Form2 is ready to receive the passed in text.
5. Open the code editor for Form1.
6. In the Button1 Click event, insert the following code.
In C#:
private void Button1_Click(object sender, EventArgs e)
{
Form2 frmForm2 = new Form2(textBox1.Text);
this.Hide();
frmForm2 .ShowDialog();
this.Show();
}
In VB:
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim frmForm2 As New Form2 (TextBox1.Text)
Me.Hide()
frmForm2 .ShowDialog()
Me.Show()
End Sub
This code creates an instance of the new form, passing in the text from the TextBox. This passes the defined value to the constructor in Form2.
It then hides itself and shows the new form as a modal dialog. After the user returns from the modal dialog, the first form appears again. You can change this logic as required by your application, as long as the first line that passes the Textbox value remains the same.
Use this technique any time you need to pass a set of data from one form to another.
Enjoy!
There are often times in most applications where you need to work with lists. You may have lists of customer types or list of settings or lists of numbers. Regardless of the type of item in your list, this post describes how to randomly pick a set of items from the list.
In this example, the code randomly picks a set of words from an array of words. But you could easily use this technique to pick items from any list or array of any strings, numbers, or objects.
In C#:
string myText = "That that is, is. That that is not, is not. " +
"Is that it? It is.";
string[] wordArray = myText.Split(' ');
// Random instance
Random rnd = new Random();
int numberOfNumbers = 6;
List<int> randomNumbers = new List<int>();
int numberCount = 0;
do
{
int randomNumber;
randomNumber = rnd.Next(0, wordArray.Length);
// Ensure this random number was not already selected
if (!randomNumbers.Contains(randomNumber))
{
randomNumbers.Add(randomNumber);
numberCount += 1;
}
} while (numberCount < numberOfNumbers);
// Display the randomly selected words
foreach (var randomNumber in randomNumbers)
{
Debug.WriteLine(wordArray[randomNumber]);
}
In VB:
Dim myText As String="That that is, is. That that is not, is not. " & _
"Is that it? It is."
Dim wordArray() As String = myText.Split(" "c)
' Random instance
Dim rnd As New Random()
Dim numberOfNumbers As Integer = 6
Dim randomNumbers As New List(Of Integer)
Dim numberCount = 0
Do
Dim randomNumber As Integer
randomNumber = rnd.Next(0, wordArray.Length)
' Ensure this random number was not already selected
If (Not randomNumbers.Contains(randomNumber)) Then
randomNumbers.Add(randomNumber)
numberCount += 1
End If
Loop While (numberCount < numberOfNumbers)
' Display the randomly selected words
For Each randomNumber In randomNumbers
Debug.WriteLine(wordArray(randomNumber))
Next
In both examples, the first two lines of code puts the words from the string into an array. This was just an easy way to build a quick array of data.
Note: If you want more information on how the Split function works, see this post.
The code then creates an instance of the Random class. The code uses this instance to generate the random numbers.
The numberOfNumbers variable defines the number of desired random numbers. In this case, I picked 6. You could set this to any value.
The randomNumbers variable is a generic list that contains the selected list of random numbers. So as the code selects the (6 in this case) random numbers, they will be added to this list.
The numberCount variable counts how many random numbers that have been selected.
The code then performs a Do loop, repeating the next set of code until the desired number of random numbers were selected.
This code cannot simply loop a defined number of times to select the defined number of random numbers. In this case, for example, it could not simply loop six times. That is because the Random function picks "with replacement", meaning that it can pick the same number multiple times.
Note: If you do want to allow random selection "with replacement", meaning that the same item can be picked from the list multiple times, then you can just loop for the defined number of times and you don't need the if statement that checks whether the random number was already picked.
Within the loop, the Next method of the Random object is used to pick a number between 0 (inclusive) and the length of the array (exclusive). Basically, since the wordArray has a length of 15 in this example, this picks random numbers between 0 and 14.
If the random number is not already in the randomNumbers list, the code adds it to the list and loops again. If the random number is in the list, it just loops to pick another number.
When the appropriate number of random numbers are obtained, the random numbers are used to access the list items. In this case, it displays the array item.
For example, on one run through of this code, the output is:
not,
not.
is
Is
is,
it?
On a second run through, the output is:
That
That
is.
not.
that
is,
Use this technique any time you want to retrieve a random set of items from a list (array or generic list).
Enjoy!
There are times when you need to work with a set of text as individual words. For example, you need to count the occurrence of each word or convert the first letter of each word to a capital letter or randomly pick specific words.
One easy way to convert a set of text into individual words is to use the Split function. (Another option is to use Regular Expressions, but that is a different post.)
In C#:
string[] wordArray= textBox1.Text.Split(' ');
In VB:
Dim wordArray() As String = TextBox1.Text.Split(" "c)
In both examples, the text entered by the user into TextBox1 is split into words and each word is stored in a position of the array.
The split parameter is a char value. In VB, you define a char using the small "c" after the single character string: Split(" "c). In C#, you define a char using single quotes instead of double quotes: Split(' ').
For example, say the user enters this:
"That that is, is. That that is not, is not. Is that it? It is."
The contents of the array then looks like this:
From here you can then work with the individual words as array elements.
Enjoy!
After my post on Using Code Snippets, several people have asked about the Code Snippet Editor.
As stated here, "the Snippet Editor allows for easy management and creation of code snippets for Visual Studio."
You can find out more and download the Snippet Editor from codeplex.
Bill McCarthy, the author of the Snippet Editor, did a very nice screen cast that walks through using the Snippet Editor. You can find that here.
Enjoy!
As stated here, you use object binding in a WinForms application by following these steps:
1. Build the business objects for your application.
2. Define a business object data source in the Windows Application
project containing your user interface.
3. Bind properties of the controls on the form to business object
properties.
The first two steps were detailed here. This post covers the third step.
NOTE: You will not be able to follow along with the information in this post unless you perform steps 1 and 2 first from my prior post.
Binding to Existing Controls
Once you have at least one data source set up in your Data Sources window, you can bind it to your user interface. The technique you use to set up the binding is slightly different, depending on whether you are working with existing controls or creating new controls. This section describes the former, and the next section details the latter.
The process of binding existing controls on a form to a data source is
referred to as connect-the-dots binding. It is as easy as connecting point
A to point B.
To bind an existing control to a business object property displayed in
the Data Sources window:
1. Open the form you want to bind in the Forms Designer.
2. Open the Data Sources window. Position the Data Sources window so that you can easily see its contents and the Forms Designer at the same time.
3. Drag the desired property from the Data Sources window, and
drop it on the appropriate control on the form to bind them.
Repeat this process for every control on the form that needs to be
bound to the business object.
The first time you drop a property from an object data source to a form, Visual Studio adds a BindingSource component to the form’s component tray and names it based on the name of your business object class. This component manages the binding between the form controls and the
business object properties.
The default property of the control that you used as your drop target is bound to the BindingSource component for the property that was dragged from the Data Sources window. You can see this by using the Properties window to examine the control’s DataBindings property.
For example, add a TextBox to an empty form. Then drag the LastName property from the Customer object data source in the Data Sources window and drop it on the TextBox control. This binds the Text property of the TextBox control to the LastName property of the Customer class by way of the CustomerBindingSource. The result is shown below.
As shown in the Properties window, the Text property of the TextBox control is now bound to the LastName property by way of the CustomerBindingSource.
The binding information for the BindingSource component and for each bound control is retained in the form’s partial class. When you perform the binding, Visual Studio sets the appropriate control properties.
The code Visual Studio adds to the partial class for the BindingSource
component is as follows
In C#:
this.customerBindingSource.DataSource =
typeof(SampleBoCSharp.Customer);
In VB:
Me.CustomerBindingSource.DataSource = GetType(SampleBoVB.Customer)
This defines that the CustomerBindingSource is associated with the Customer class in the associated project.
Visual Studio adds code to the partial class for each bound control as
follows
In C#:
this.textBox1.DataBindings.Add(
new System.Windows.Forms.Binding("Text",
this.customerBindingSource, "LastName", true));
In VB:
Me.TextBox1.DataBindings.Add( _
New System.Windows.Forms.Binding("Text", _
Me.CustomerBindingSource, "LastName", True))
This code adds a binding entry for the TextBox control. The binding entry defines that the Text property of the TextBox is bound to the LastName property in the CustomerBindingSource. The last parameter defines that formatting is enabled to allow formatting of the value.
NOTE: Although you do not normally need to look at this generated code, there is one case where you may need to know about it. If you use the renaming feature, you will see that the Rename renames only direct references to the property or method, not any occurrences in quoted strings. So if you use
the rename feature and rename LastName to CustomerLastName, your binding no longer works, because it is still using LastName. You must locate any partial class code that references the property as a quoted string and manually change its name.
Before you can successfully run the application, you need to write some code. So far, you have defined that the BindingSource component binds to a business object. And you have defined which properties of the business object are to appear in which control of the form. However, you did not define which business object it binds to.
To define which instance of the business object to bind to, assign the DataSource property of the BindingSource component to a business object instance as follows:
In C#:
Customer cust = new Customer()
{LastName = "Baggins",
FirstName = "Bilbo"};
this.customerBindingSource.DataSource = cust;
In VB: Dim cust= New Customer With _
{.LastName = "Baggins", _
.FirstName = "Bilbo"}
Me.CustomerBindingSource.DataSource = cust
This example binds the controls in the user interface to a new instance of the Product class. Place this binding code in the form. The specific location in the form depends on when you want the binding to occur. If you want the binding to occur when the form is loaded, add this code to the form’s Load event.
By setting that instance as the DataSource for the CustomerBindingSource, the runtime uses the properties associated with that instance to populate any controls on the form bound to the CustomerBindingSource. So by writing only two lines of code, when you run your application, your form appears populated with appropriate data.
If the user changes a value in a control on the form, the runtime assigns the revised value to the associated property.
NOTE: The business object property is assigned to the value from the control when the user leaves the control. By default, if the user modifies a value and then closes the form before leaving the control, the bound property is not changed. However, if you call the Validate method, the current control’s value is assigned to its associated property even if the user does not leave the control.
Binding to New Controls
Now for the really fun part. In addition to binding to existing controls you can use the features of the Data Sources window to add new controls to a form. Or you can use it to automatically create controls for all of the business object properties.
Adding New Controls Using the Data Sources Window
If you want to add a new control to a form, you can add the control from the Forms Designer toolbox, add the associated Label control, and then bind the new control using the techniques from the preceding section.
But the Data Sources window provides a shortcut for this process. If you drag a property from the Data Sources window and drop it on the Forms Designer, Visual Studio creates the control, binds it, and creates the associated Label control. All you need to do is drag and drop!
To add a new control to a form:
1. Open the form in the Forms Designer.
2. Open the Data Sources window. Position the Data Sources window so that you can easily see its contents and the Forms Designer at the same time.
If a Forms Designer window is active, the Data Sources window changes so that each object data source and each property under the object data source displays an icon to the left of the name. This icon indicates the type of control that Visual Studio creates if you drag the item to the Forms Designer.
3. Drag a property from the Data Sources window, and drop it on the
Forms Designer.
Visual Studio automatically creates the control associated with the dropped property and binds it. It even gives the control a valid name using the property name and the control type, such as FirstNameTextBox—no lame TextBox1 name. In addition, Visual Studio creates an appropriate Label control. Visual Studio is smart about generating the text for the Label control. It breaks the property name into separate words based on either alphabetic casing or underscores.
For example, if you drag the FirstName property from the Data Sources window and drop it in the CustomerWin form, Visual Studio creates a TextBox control for the First Name and an appropriate Label control.
But what if you don’t want the property to render as a TextBox control? You can change the type of control that is rendered using the Data Sources window.
To change the default control type associated with a property:
1. Ensure that a Forms Designer is active.
2. Open the Data Sources window.
3. Click a property in the Data Sources window. The property item changes to a drop-down control list.
4. Drop down the control list and select the desired control type as shown in the prior screen shot of the Data Sources Window.
Select Customize from the list to select a control type that is not on the list. You can select just about any type of control, including third-party and
custom controls.
NOTE: Before a third-party control can be added as a control type in the Data Sources window, it must first be added to the Forms Designer toolbox.
When you drag the control from the Data Sources window to the Forms Designer, Visual Studio renders the type of control you selected as the default control type for the property. The property retains its default control type in the Data Sources window.
Creating a Form from the Data Sources Window
But wait—there’s more! You can use the Data Sources window to create all the controls for a business object and bind them.
To create a new bound form:
1. Add a new Windows Form to your project.
2. Open the Data Sources window. Position the Data Sources window so that you can easily see its contents and the Forms Designer window at the same time.
3. In the Data Sources window, click the object data source you want
to bind to this form. The item changes to a drop-down control list.
4. Use the drop-down control list to define how Visual Studio renders controls on the form for this object data source.
The class object data source can be rendered as Details, DataGridView, or None, or you can select Customize from the control list to select a control type that is not on the list.
For a data entry form, set the control type for the object data source to Details. Visual Studio renders individual controls for each property in the object data source, along with associated Label controls. Select DataGridView to render the properties in the object data source as columns in a grid.
5. Drag the object data source node from the Data Sources window and drop it on the form.
NOTE: If you plan to use Panel or other container controls on the form, place the Panel controls or other containers on the form first, and then drag the object data source node and drop it on the desired container.
Bang! Visual Studio automatically creates the controls as you specified and binds them. If you selected Details rendering, Visual Studio creates a control based on the default control type for each object data source property in the Data Sources window. It sets each control’s Name property based on the name of the associated business object property and control type. Visual Studio also defines an appropriate Label control for each control it creates. If you selected DataGridView rendering, Visual Studio defines a grid with appropriate columns and column header text.
For example, if you set up the Customer object data source to render as Details and then drag the Customer node from the Data Sources window and drop it on a form, Visual Studio creates a TextBox control for every property, binds it, names it, and creates a Label control, as shown below.
Notice that this process created two components in the form’s component tray. The BindingSource is the component that manages the binding. The BindingNavigator added VCR-style controls to the top of the form. This type of user interface is not recommended for most business applications. (How often does a user want to sequentially navigate through every customer, for example?) You can delete the BindingNavigator component, and Visual Studio automatically deletes the VCR-style controls.
The labels created for the controls are generated based on the property names. Visual Studio is smart enough to add spaces between words based on underscores or alphabetic casing. This provides a great starting point for your user interface.
The controls are added in alphabetical order, which may not be the most logical order or placement for your users. You can move and size the controls as desired. After you have the controls in their desired order, use the Tab Order view to reset the tab order.
All you need then are those two lines of code in the form’s Load event to
define the instance of the business object that is bound to the controls:
In C#:
Customer cust = new Customer()
{LastName = "Baggins",
FirstName = "Bilbo"};
this.customerBindingSource.DataSource = cust;
In VB: Dim cust= New Customer With _
{.LastName = "Baggins", _
.FirstName = "Bilbo"}
Me.CustomerBindingSource.DataSource = cust
When you run the application, the runtime automatically populates the
controls on the form.
The Data Sources window allows you to create all the controls on a form and bind them to a business object with one drag-and-drop operation and two lines of code. It has the flexibility to allow you to define the type of control to render for each property in the object data source.
Using these techniques can make quick work of building and maintaining
your user interface.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
As stated here, you use object binding in a WinForms application by following these steps:
1. Build the business objects for your application.
2. Define a business object data source in the Windows Application
project containing your user interface.
3. Bind properties of the controls on the form to business object
properties.
Building the Business Objects
The idea with object binding is to bind your business objects to your user interface controls. So the first step is to create the business objects. This example builds a Customer class.
NOTE: Add a Class Library project to your solution and create the Customer class in the Class Library project. You could add the Customer class to your WinForms project, but it is not recommended.
In C#:
public class Customer
{
public int CustomerId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string EmailAddress { get; set; }
}
The above code takes advantage of automatically implemented properties. VB does not yet have this feature, but it is expected with VB 10.
In VB:
Public Class Customer
Private _CustomerId As Integer
Public Property CustomerId() As Integer
Get
Return _CustomerId
End Get
Set(ByVal value As Integer)
_CustomerId = value
End Set
End Property
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
Private _LastName As String
Public Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal value As String)
_LastName = value
End Set
End Property
Private _EmailAddress As String
Public Property EmailAddress () As String
Get
Return _EmailAddress
End Get
Set(ByVal value As String)
_EmailAddress = value
End Set
End Property
End Class
Configuring a Data Source
In Visual Studio, a data source defines the source of data, such as a business object or a database. To use a data source to bind your user interface, you need to configure the data source for the project containing your user interface, such as your Windows Application project.
For object binding, each data source represents a single business object class. This means that you configure a data source for each business object class that you want to bind. Luckily, this process is quick and easy.
When you use object binding, the data source is referred to as an object data source. This is technically accurate because at runtime, the binding binds to a specific business object. However, this can look a little confusing at design-time because each data source represents a single business object class. The Data Sources window displays each object data source with the class name and lists the public properties of the class. You then bind each property of the class to a control on the form.
To set up an object data source for your user interface:
1. Build your business object Class Library project.
Only compiled business object classes are recognized by the Data
Source Configuration Wizard and the Data Sources window.
2. Select the Windows Application project in Solution Explorer.
NOTE: Always ensure that the Windows Application project is selected in
Solution Explorer before you work with the Data Sources window.
3. Select Data | Show Data Sources from the main menu bar.
The Data Sources window is displayed.
4. Click the Add New Data Source link in the Data Sources window, or click the Add New Data Source button on the Data Sources window toolbar, or select Data | Add New Data Source from the main menu bar.
NOTE: The Add New Data Source link only appears when the Data Sources
window is empty.
This launches the Data Source Configuration Wizard. The first page, shown below, allows you to select the source of the data.
5. Select Object for object binding, and click Next.
The second page of the Data Source Configuration Wizard, shown below, provides the list of classes for your selection.
The tree view only lists the classes in the Windows Application
project and classes in any component referenced by the Windows
Application project. If you already have a reference to your business object Class Library component, the component appears in the Data Source
Configuration Wizard. Otherwise, you can add a reference using the Add Reference button.
6. Use the tree view to navigate to the class you wish to use for object binding.
7. Select the desired class, and click Finish.
The object data source for the selected class is added to the Data
Sources window, as shown below.
NOTE: The Data Sources window lists only public class properties.
Use the Data Source Configuration Wizard to define a data source for every business object class you plan to use for binding.
If you are curious about where the Data Sources window stores its information, it is in a set of XML files. Select the Windows Application project in Solution Explorer, and click the Show All Files button on the Solution Explorer toolbar. All the system files for the project are then accessible from Solution Explorer. Open the Properties node (My Project node in VB) under the Windows Application project, and open the DataSources node to see the XML files for the data sources in the Data Sources window.
Creating data sources is quick and easy. Add a data source to the Windows Application project for any business object class that you want to use for binding to your user interface.
Bind the User Interface Controls
There are many ways to bind the resulting object data source to your user interface. These are covered in a later post.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
Before going through the details of how to use object binding, it is important to understand exactly what it is—and what it is not. Object binding is binding your business object properties to user interface elements. Object binding is not database binding in the strict sense of the term. It does not directly collect or bind any data from your database.
When you are using business object classes without object binding, the flow of data from the database to your user interface and back again requires these steps:
1. The business object calls the data access component to get the data from the database and sets the business object properties using that data.
For example, the Product class calls the data access component, which uses a query or stored procedure to fill a DataTable from the Product table. The data access component returns the DataTable to the Product class, which assigns each field from the table to a property of the object. To illustrate, the line of code required to get the ProductName field from the DataTable and set the
ProductName property is as follows:
In C#:
myProduct.ProductName = dt.Rows[0]["ProductName"];
In VB:
myProduct.ProductName = dt.Rows(0).Item("ProductName")
2. The user interface component accesses the business object properties to fill the values of the controls on the form.
For example, each control on the ProductWin form is assigned to the value of the appropriate Product business object property. To illustrate, the line of code required to set the Text property of the Name TextBox control to the ProductName property of the Product business object is as follows:
In C#:
NameTextBox.Text = myProduct.ProductName;
In VB:
NameTextBox.Text = myProduct.ProductName
3. After the user makes any changes, the user interface component assigns the current values in the controls back to the business object properties.
For example, the value in each control on the ProductWin form is assigned back to its associated Product business object property. To illustrate, the line of code required to set the ProductName property to the current value in the Name TextBox control is as follows:
In C#:
myProduct.ProductName = NameTextBox.Text;
In VB:
myProduct.ProductName = NameTextBox.Text
4. The business object component updates the DataTable using the property values and passes it back to the data access component, which updates the database with the changed data.
For example, the value of each Product business object property is assigned to the associated field in the DataTable, and the result is passed to the data access component, which updates the Product table. To illustrate, the line of code required to set the ProductName field in the DataTable to the value of the ProductName business object property is as follows:
In C#:
dt.Rows[0]["ProductName"] = myProduct.ProductName;
In VB:
dt.Rows(0).item("ProductName") = myProduct.ProductName
Using object binding allows you to skip steps 2 and 3. Object binding automatically populates the controls on the user interface from the business object properties. As the users change the contents of the controls, object binding updates the associated business object properties, keeping them in synchronization.
That still leaves steps 1 and 4 for you. This link provides information on building a data access component to handle steps 1 and 4.
In summary, object binding is the process of binding control properties directly to properties of your business objects. For example, you could bind the Text property of a TextBox control to the ProductName property of a Product business object. When the form is displayed, the runtime automatically displays the value of the ProductName property in the TextBox. And if the user changes the text in the TextBox control, the runtime modifies the ProductName property accordingly. This saves you from writing the code required to transfer data back and forth between the controls on the user interface and the business object properties.
Visual Studio provides design-time tools for working with your business objects as data sources for your user interface, making it easy to bind each control to its associated business object property. The only requirement for your business objects to work with these tools is that the business object class needs at least one public property. No specific constructors, interfaces, or attributes are needed.
Object Binding Versus Data Binding
Don’t confuse the term object binding with the more generalized term data binding. Data binding is the broad term for binding control properties to data from any data source. Object binding is just one type of data binding. Some common types of data binding are as follows:
- Binding to tables in a database (Visual Studio generates code to
define a typed DataSet and TableAdapters) - Binding to stored procedures in a database (Visual Studio generates
code to define a typed DataSet and TableAdapters) - Binding to a business object (object binding does not generate code;
it just sets control properties) - Binding to an array or collection of data
- Binding to a Web service
When binding to a database, Visual Studio generates a significant amount of code and then binds the user interface to that generated code. Object binding binds to your code. That gives you much more control and greatly simplifies the maintenance of your application.
Using Object Binding
You use object binding by following these steps:
1. Build the business objects for your application.
2. Define a business object data source in the Windows Application
project containing your user interface.
3. Bind properties of the controls on the form to business object
properties.
Although it is much easier to think about object binding as a direct binding of a control’s property to a specific business object’s property, object binding frequently uses a BindingSource component as an intermediary. A BindingSource is a component on a form that binds the controls on the form to the business object. Each control is bound to the BindingSource component, which in turn is bound to the business object. This makes it much easier to change the binding for all controls by changing the BindingSource without having to separately rebind each control.
You set the BindingSource to an individual business object instance in your code. The runtime then binds all the properties associated with that instance to the controls, thereby displaying the business object property values in the controls. And as the user changes the content of any controls, the business object property values are changed accordingly.
A form can contain multiple BindingSource components. For example, a ProductWin form can contain product data and display a drop-down list of product types. You can define a BindingSource component for the product data and a second BindingSource component for the product type data.
(Based on an except from "Doing Objects in Visual Basic 2005".)
For more information on object binding, see these links:
Enjoy!
Are there some pieces of code that you find yourself writing over and over and over again? Do you ever use a piece of code so seldom that you always need to look it up? Some of these pieces may make sense as standard components or standard methods in an application framework, like logging or validation (see this link for an example Validation class), so you never need to write them or look them up again. Other pieces, like Property statements, loops, exception handlers, or file input/output, are more unique and cannot easily be made into standard methods. For these types of code pieces, code snippets are a perfect answer.
A code snippet is a prebuilt intelligent piece of code, sometimes referred to as an expansion template, that you can easily insert into the Code Editor. Visual Studio comes with a large library of built-in code snippets for Visual Basic and for C#. You can also build your own code snippets.
Using code snippets makes it quick to add predefined code pieces to your application. Creating your own code snippets allows you to create a library of custom code pieces and share them with other developers.
This post demonstrates how you can insert existing code snippets into your application. A later post will describe how to build your own code snippets.
Code snippets include standard programming constructs, such as loops and Property statements, to help you quickly build common code blocks. For VB, code snippets also include common programming tasks, such as creating an MDI child form, to help you quickly insert commonly used code. The VB code snippets also include many lesser-used programming techniques, such as drawing a filled rectangle, to provide assistance with tasks that you don’t use as often and would otherwise need to look up. With all of this functionality available at your fingertips, code snippets can greatly enhance your productivity.
To insert a code snippet into the Code Editor use the following steps.
In C#:
1. In the Code Editor, place the insertion point where you wish to insert the snippet.
2. Right-click and select Insert Snippet from the context menu. The Code Snippet Picker appears:
3. Double-click or press the Tab key on the snippet folders to navigate
the hierarchy and find the desired snippet. To back up in the hierarchy, press the Backspace key or press Shift+Tab. To cancel the Code Snippet Picker, press the Esc key.
4. Double-click on the desired snippet. Visual Studio inserts the code associated with the selected snippet into the Code Editor at the current insertion point.
In VB:
1. In the Code Editor, place the insertion point where you wish to insert the snippet.
2. Right-click and select Insert Snippet from the context menu, or
type a question mark (?) and press the Tab key. The Code Snippet Picker appears:
3. Double-click or press the Tab key on the snippet folders to navigate
the hierarchy and find the desired snippet. To back up in the hierarchy, press the Backspace key or press Shift+Tab. To cancel the Code Snippet Picker, press the Esc key.
4. Double-click on the desired snippet. Visual Studio inserts the code associated with the selected snippet into the Code Editor at the current insertion point.
Snippets also have shortcuts to minimize the steps required to access a
snippet. The shortcut associated with a snippet is displayed in the ToolTip
of the Code Snippet Picker entry.
In C#:
In VB:
To insert a code snippet using the shortcut…
In C#:
1. In the Code Editor, place the insertion point where you want to insert the snippet.
2. Type the shortcut, press the Tab key, and press the Tab key again. Visual Studio inserts the code associated with the selected snippet into the Code Editor at the current insertion point.
In VB:
1. In the Code Editor, place the insertion point where you want to insert the snippet.
2. Type the shortcut and press the Tab key. Visual Studio inserts the code associated with the selected snippet into the Code Editor at the current insertion point.
For example, when you’re building classes, one of the more tedious
tasks is defining all the properties. You have to create a private backing
variable and a property getter and setter for every property. A more efficient way to create your properties is to use the property code snippet.
In C#:
Use one of the following techniques:
- Display the Code Snippet Picker, navigate the snippet folders and
then double-click on the defined snippet name.
To insert the property code snippet, select Visual C#| prop.
- Use the shortcut for the code snippet by typing the shortcut name
in the Code Editor and pressing the Tab key two times.
To insert the property code snippet, type prop, press the Tab key, and then the press the Tab key again.
- Type part of a shortcut name and use the Intellisense drop down to pick the snippet.
To insert the property code snippet, type "pro" and Intellisense displays all snippets that begin with those letters. Press the Tab key to select the item from Intellisense, and then press the Tab key again to insert the snippet.
Regardless of the technique you used when the property snippet is inserted into the Code Editor, the following code is generated:
Notice that this uses the auto-implemented property syntax.
In VB:
Use one of the following techniques:
- Display the Code Snippet Picker, navigate the snippet folders and
then double-click on the defined snippet name.
To insert the Define a Property code snippet, select Code Patterns – … | Properties, Procedures, Events | Define a Property.
- Use the shortcut for the code snippet by typing the shortcut name
in the Code Editor and pressing the Tab key.
To insert the Define a Property code snippet, type property and then the press the Tab key.
- Type part of a shortcut name and use the Intellisense drop down to pick the snippet.
To insert the Define a Property code snippet, type "pro" and Intellisense displays all snippets that begin with those letters. Press the Tab key to select the item from Intellisense, and then press the Tab key again to insert the snippet.
Regardless of the technique you used when the Define a Property snippet is inserted into the Code Editor, the following code is generated:
Many snippets contain highlighted text, called replacements (shown in green in the above two screen shots). A replacement is a variable or other text in the snippet that you can replace with a unique value when you insert the snippet. Modifications to a replacement cascade through the snippet, so changing any replacement automatically changes all other replacements with the same text.
Visual Studio makes it easy to modify the replacements. When Visual Studio inserts the snippet, it sets focus to the first replacement (shown in gray in the above two screen shots). You type the desired value for the replacement and press the Tab key. Visual Studio cascades your replacement value through the snippet and moves the focus to the next replacement. This process is repeated until all replacement values are entered or until you leave the snippet.
For example, to define a FirstName property…
In C#:
Type string in the first replacement (int) and press the Tab key twice. Then type FirstName in the next replacement (MyProperty) and press the Enter key and you are done. The result is as follows:
public string FirstName { get; set; }
In VB:
Type _FirstName in the first replacement (newPropertyValue) and press the Tab key. Notice how the value is cascaded to the getter and setter. Type String in the second replacement and press the Tab key (or since the default is String, just press the Tab key). Finally, type FirstName in the next replacement (NewProperty) and press the Tab key, and you are done. The result is as follows:
Private _FirstName As String
Public Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal value As String)
_FirstName = value
End Set
End Property
If the snippet you insert requires a reference or import that you don’t currently have set, Visual Studio automatically adds the reference and associated import when you insert the snippet.
Inserting snippets into your code is quick and easy and saves you from all that typing. Using snippets can also save you time, because they provide prebuilt code for tasks that you may not perform very often.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
The Enumerable class is new in .NET 3.5 and is part of the System.Linq namespace. It provides a set of static methods that allow you to query any object that implements IEnumerable, basically meaning any object that supports a for/each.
This post focuses on the Repeat method of the Enumerable class and some of the helpful things that this class can do for you.
NOTE: Be sure to set a reference to System.Core.
First, at the top of your code file, add this code.
In C#:
using System.Linq;
In VB:
Imports System.Linq
(Or for VB you can import a namespace using the project properties.)
You can then use the Enumerable.Repeat as shown in the following examples.
In C#:
// Initialize an array to -1 for each of 10 elements
int[] all1 = Enumerable.Repeat(-1, 10).ToArray();
// Initialize an array to "A" for each of 10 elements
string[] allA = Enumerable.Repeat("A", 10).ToArray();
// Convert a single value to an array
int value = 42;
int[] valueArray = Enumerable.Repeat(value, 1).ToArray();
// Generate 10 random numbers
Random rand = new Random();
int[] randomArray = Enumerable.Repeat(0, 10).Select(
i => rand.Next(0,10)).ToArray();
// Initialize a list with 5 Customer objects
List<Customer> custList = Enumerable.Repeat(
new Customer(), 10).ToList();
In VB:
' Initialize an array to -1 for each of 10 elements
Dim all1() As Integer = Enumerable.Repeat(-1, 10).ToArray()
' Initialize an array to "A" for each of 10 elements
Dim allA() As String = Enumerable.Repeat("A", 10).ToArray()
' Convert a single value to an array
Dim value As Integer = 42
Dim valueArray() As Integer = Enumerable.Repeat(value, 1).ToArray()
' Generate 10 random numbers
Dim rand As Random = New Random()
Dim randomArray() As Integer = Enumerable.Repeat(0, 10).Select( _
Function(i) rand.Next(0, 10)).ToArray()
' Initialize a list with 5 Customer objects
Dim custList As List(Of Customer) = Enumerable.Repeat( _
New Customer(), 10).ToList()
The first example initializes an array of 10 integers to –1.
The second example initializes an array of 10 strings to "A".
The third example takes a single value and creates an array from it. This is often necessary when a method requires an array instead of a single element.
The random number example provides another way to generate a random numbers. In this case, it generates 10 random numbers between 0 and 9. Note that this does not ensure uniqueness, so the number 2 for example could occur multiple times in the list.
The last example initializes a list of business objects to an empty set of objects. You could use object initializer syntax in this example to initialize all of the objects to some set of values, such as setting the State = "CA". This could be useful in setting up data for testing or prototyping.
Use the Repeat method any time you want to repeat values in an array or list.
Enjoy!
The Enumerable class is new in .NET 3.5 and is part of the System.Linq namespace. It provides a set of static methods that allow you to query any object that implements IEnumerable, basically meaning any object that supports a for/each.
This post focuses on the Range method of the Enumerable class and some of the helpful things that this class can do for you.
NOTE: Be sure to set a reference to System.Core.
First, at the top of your code file, add this code.
In C#:
using System.Linq;
In VB:
Imports System.Linq
(Or for VB you can import a namespace using the project properties.)
You can then use the Enumerable.Range as shown in the following examples.
In C#:
// Initialize an array of numbers 0 - 9
int[] numbers = Enumerable.Range(0, 10).ToArray();
// Initialize an array with any arithmetic sequence
// This one does 10, 20, 30 ... 100
int[] arithmetic = Enumerable.Range(0, 10).Select(
i => 10 + (10 * i)).ToArray();
// Initialize an array with any geometric sequence
// This one does 2, 6, 18, ... 39366
int[] geometric = Enumerable.Range(0, 10).Select(
i => 2 * (int)Math.Pow(3,i)).ToArray();
// This one does 10, 5, 2.5, ... 0.01953125
double[] geometric2 = Enumerable.Range(0, 10).Select(
i => 10 * Math.Pow(.5, i)).ToArray();
// Initialize an array with letters
// This one does A, B, ... J
string[] letters = Enumerable.Range(0, 10).Select(
i => ((char)('A' + i)).ToString()).ToArray();
// This one does z, y, ... q
string[] letters2 = Enumerable.Range(0, 10).Select(
i => ((char)('z' - i)).ToString()).ToArray();
In VB:
' Initialize an array of numbers 0 - 9
Dim numbers() As Integer = Enumerable.Range(0, 10).ToArray()
' Initialize an array with any arithmetic sequence
' This one does 10, 20, 30 ... 100
Dim arithmetic() As Integer = Enumerable.Range(0, 10).Select( _
Function(i) 10 + (10 * i)).ToArray()
' Initialize an array with any geometric sequence
' This one does 2, 6, 18, ... 39366
Dim geometric() As Integer = Enumerable.Range(0, 10).Select( _
Function(i) CType(2 * (3 ^ i), Integer)).ToArray()
' This one does 10, 5, 2.5, ... 0.01953125
Dim geometric2() As Double = Enumerable.Range(0, 10).Select( _
Function(i) 10 * (0.5 ^ i)).ToArray()
' Initialize an array with letters
' This one does A, B, ... J
Dim letters() As String = Enumerable.Range(0, 10).Select( _
Function(i) (Chr(Asc("A") + i)).ToString()).ToArray()
' This one does z, y, ... q
Dim letters2() As String = Enumerable.Range(0, 10).Select( _
Function(i) (Chr(Asc("z") - i)).ToString()).ToArray()
Note: Even though all of the above examples use ToArray to build an array, you could instead build lists using ToList.
The first example uses the basics of the Range to build an array of integers. You can use this syntax to build any range of integers.
The other examples combine the Range method with a lambda expression to build other types of numeric arrays.
The arithmetic sequence example demonstrates how to build an array where the difference between each term is a constant. In this example, the constant is 10.
The geometric sequence examples demonstrates how to build any array where each term after the first is generated by multiplying the previous one by a fixed non-zero number (common ratio). In the first example, the common ratio is 3. In the second example, the common ratio is 1/2.
The alphabetic sequence uses the fact that the Unicode characters for the letters in the alphabet are in numerical sequence. So adding 1 to 'A' gives you 'B' and so on.
Use the Range method any time you want to build a list or array of values in a logical sequence.
Enjoy!
A data type is said to be nullable if it can be assigned a value or a null reference. Reference types, such as strings and class types, are nullable; they can be set to a null reference, and the result is a null value. Value types, such as integers, Booleans, and dates, are not nullable. If you set a value type to a null reference, the result is a default value, such as 0 or false.
Try this:
In C#:
int i = null;
Boolean b = null;
DateTime d = null;
Debug.WriteLine(i + ", " + b + ", " + d.ToShortDateString());
In VB:
Dim i As Integer = Nothing
Dim b As Boolean = Nothing
Dim d As DateTime = Nothing
Debug.WriteLine(i & ", " & b & ", " & d.ToShortDateString)
The C# code will generate compile-time errors saying you cannot convert null to the defined type because it is a non-nullable value type.
VB, however, will display this:
0, False, 1/1/0001
Instead of retaining a null value, VB sets the variable to the default value.
A value type can express only the values appropriate to its type; there is no easy way for a value type to understand that it is null.
There may be cases, however, when you need your code to really handle a null as a null and not as a default value. It would be odd, for example, to handle a null date by hard-coding a check for the 1/1/0001 date.
The .NET Framework 2.0 introduced a Nullable class and an associated Nullable structure. The Nullable structure includes the value type itself and a field identifying whether the value is null. A variable of a Nullable type can represent all the values of the underlying type, plus an additional null value. The Nullable structure supports only value types because reference types are nullable by design.
To make a value type property nullable, you need to declare it using the Nullable structure. However, you still want your property to be strongly typed as an integer, date, Boolean, or the appropriate underlying type. The ability to use a class or structure for only a specific type of data is the purpose of generics.
Generics allow you to tailor a class, structure, method, or interface to a specific data type. So you can create a class, structure, method, or interface with generalized code. When you use it, you define that it can work only on a particular data type. This gives you greater code reusability and type safety.
The .NET Framework built-in Nullable structure is generic. When you use the structure, you define the particular data type to use. As a specific example, an InventoryDate property that allows the date to be a date or a null value uses the generic Nullable structure as follows:
In C#:
public Nullable<DateTime> InventoryDate { get; set; }
In VB:
Private _InventoryDate As Nullable(Of DateTime)
Public Property InventoryDate() As Nullable(Of DateTime)
Get
Return _InventoryDate
End Get
Set(ByVal value As Nullable(Of DateTime))
_InventoryDate = value
End Set
End Property
Notice the syntax of the Nullable structure. Since it is a generic structure, it has the standard <T> [C#] or (Of T) [VB] syntax, where T is the specific data type you want it to accept. In this case, the Nullable structure supports dates, so the <DateTime> or (Of DateTime) syntax is used. This ensures that the Nullable structure contains only a date or a null value.
More commonly, however, the nullable structure is defined using the question mark (?) short-hand syntax:
In C#:
public DateTime? InventoryDate { get; set; }
In VB:
Private _InventoryDate As DateTime?
Public Property InventoryDate() As DateTime?
Get
Return _InventoryDate
End Get
Set(ByVal value As DateTime?)
_InventoryDate = value
End Set
End Property
Both examples work the same way, but many developers prefer the shorter, question mark syntax.
You can then use this property in your application as needed. For
example:
In C#:
Product prod = new Product();
if (prod.InventoryDate.HasValue)
{
if (prod.InventoryDate.Value < DateTime.Now.Date.AddDays(-10))
{
MessageBox.Show("Need to do an inventory");
}
}
else
{
MessageBox.Show("Need to do an inventory - never been done");
}
In VB:
Dim prod As New Product
If prod.InventoryDate.HasValue Then
If prod.InventoryDate.Value < Now.Date.AddDays(-10) Then
MessageBox.Show("Need to do an inventory")
End If
Else
MessageBox.Show("Need to do an inventory - never been done")
End If
The HasValue property of the Nullable class defines whether the value type has a value—in other words, whether it is null. If it does have a value, you can retrieve the value using the Value property of the Nullable class.
The Nullable structure is exceptionally useful when you’re working with databases, because empty fields in a database are often null. Assuming that you have an InventoryDate fi eld in a table, you could write code as follows:
In C#:
prod.InventoryDate =
dt.Rows[0]["InventoryDate"] == DBNull.Value ?
null :
(DateTime?)dt.Rows[0]["InventoryDate"];
In VB:
prod.InventoryDate = _
If(dt.Rows(0).Item("InventoryDate") Is DBNull.Value, _
Nothing, _
CType(dt.Rows(0)("InventoryDate"), DateTime))
The ternary operator is required here because you cannot convert a DBNull
to a date, so you need to ensure that it is not a null.
Use the Nullable structure any time you need to support nulls in a
value type, such as an integer, Boolean, or date.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
Application failures will occur. The user could perform a sequence of
operations or enter a value that you don’t expect, the connectivity to your
database could be interrupted, the code could have a logic error, or many
other possibilities. The .NET Framework provides structure exception
handling (SEH) to help you manage application failures.
An exception is a failure that occurs in your application. Exceptions
fall into two broad categories: expected and unexpected.
Expected exceptions are those that your application plans for and
responds to. For example, you expect that a user-requested customer may
not be found in the database. So you add code to check for this condition
and respond with an appropriate message to the user.
Unexpected exceptions are those that you know may happen, but
don’t necessarily know when or where. These are more difficult to plan for.
Logic errors are a good example of this type of exception. This is especially
true in cases where the logic error corrupts some underlying data and the
exception occurs later when that data is processed, possibly far from the
actual logic error.
Structured exception handling allows you to define a code structure
to handle both expected and unexpected exceptions generated in your application. This is the purpose of the Try/Catch blocks provided in the .NET
Framework.
The goal of exception handling is to never allow the application to abort
with a system error. Your application should handle all expected and unexpected exceptions.
In the case of unexpected exceptions, handle the exception by logging exception details to a file or some other source so that you can more quickly determine the cause of the exception. Then you can display a user-friendly message to the user and terminate the application gracefully.
To catch every possible exception in a Windows Forms application, you need to do one of the following:
- Ensure that every application entry point has a Try/Catch block to
catch every possible exception.
This is actually much harder than it sounds. Since your application
is event-driven, every event is a possible entry point into your application code. So you would have to add a Try/Catch block to every
event handler in your application. - Write code in the appropriate unhandled exception event.
This post focuses on the second option: writing code in an appropriate unhandled exception event. Implement a global exception handler in a Windows Forms application as follows.
In C#:
In C#, you can trap the ThreadException event.
- Select a Windows Application project in Solution Explorer.
- Open the generated Program.cs file by double-clicking on it.
- Add the following line of code to the top of the code file:
using System.Threading;
- In the Main() method, add the following as the first line of the method:
Application.ThreadException +=
new ThreadExceptionEventHandler(Application_ThreadException);
- Add the following below the Main() method:
static void Application_ThreadException(
object sender, ThreadExceptionEventArgs e)
{
// Do logging or whatever here Application.Exit();
}
- Add code to handle the unhandled exception within the event
handler.
Any exception that is not handled anywhere else in the application is
handled by the above code. Most commonly, this code should log the error and display a message to the user.
NOTE: This code does not really "handle" the error. It just provides a place to log the error, perform any clean up operations, and display a message to the user. Unless you exit the application as shown in the sample code above, the code will attempt to continue, but will most likely be in an invalid state.
NOTE: This code only handles unhandled exceptions from the main UI thread. If you create other threads, they will need their own exception handling.
In VB:
With VB, Windows applications provide application events. One of these application events is the UnhandledException event, which the application generates whenever an unhandled exception occurs. You can write code for this event to catch any exception not specifically handled by your application.
- Select a Windows Application project in Solution Explorer.
- Open the Project Designer for the project by right-clicking the
project and selecting Properties from the context menu, OR select
Project | Properties from the main menu bar, OR double-click on
the My Project folder under the project.
- Select the Application tab of the Project Designer if it is not
already selected.
- Ensure that the Enable application framework checkbox is
checked.
- Click the View Application Events button.
An ApplicationEvents.vb code file is added to your project, if it does
not already exist, and is opened. The generated code is as follows: Namespace My
' The following events are available for MyApplication:
'
' Startup: Raised when the application starts, before the startup form is created.
' Shutdown: Raised after all application forms are closed. This event is not raised if the application terminates abnormally.
' UnhandledException: Raised if the application encounters an unhandled exception.
' StartupNextInstance: Raised when launching a single-instance application and the application is already active.
' NetworkAvailabilityChanged: Raised when the network connection is connected or disconnected.
Partial Friend Class MyApplication
End Class
End Namespace
- Select (My Application Events) from the Class Name dropdown
at the top left of the Code Editor.
- Select UnhandledException from the Event drop-down at the
top right of the Code Editor. The following event handler code lines are generated:
Private Sub MyApplication_UnhandledException( _
ByVal sender As Object, ByVal e As _
Microsoft.VisualBasic.ApplicationServices.UnhandledExceptionEventArgs) _
Handles Me.UnhandledException End Sub
- Add code to handle the unhandled exception within the event
handler.
Any exception that is not handled anywhere else in the application is
handled by this code. Most commonly, this code should log the error and
display a message to the user.
NOTE: The VB UnhandledException event never occurs when you are running
with the debugger. When you are running with the debugger, Visual Studio catches the error and assists you with debugging it. To test the code in this event, use the option to "Start Without Debugging".
By setting up a global exception handler, you can ensure that your application catches any unexpected exception and never displays an unfriendly message to your users.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
The four basic elements of an object-oriented system are abstraction,
encapsulation, inheritance, and polymorphism. This post defines
these terms and describes why they are important to software design and
development.
[To begin with an overview of OO, start here.]
Abstraction: Focusing on What is Important
Abstraction is a technique that we all use to manage the complexity of the
information we collect every day. It allows us to recognize how things are
similar and ignore how they are different, to think about generalities and
not specifics, and to see what things are without thinking about what makes
them that way. You can abstract important characteristics at any given time
and for any particular purpose and ignore all other aspects.
How you develop an abstraction depends on both its purpose and your
perspective. For example, on a warm summer day I look at a tree and
abstract it as a shade provider. A young child abstracts it as a place to
climb. One tree, two different abstractions.
Abstraction is used to identify the objects involved with a particular
application. In developing a payroll system, you would think about Jessica
Jones and abstract her as an employee, thinking only about her salary and
how she gets paid. When working on the company softball league application, you would abstract Jessica as a player and be more concerned with her position and batting average. One object, two completely different abstractions.
Scenarios (or use cases) can ensure that the correct abstraction is used in a particular application. They provide context for the objects, giving them a purpose.
Using abstraction, you can focus on the objects and not on the implementation. This lets you think about what needs to be done and not how the computer will do it.
Encapsulation: Hiding Your Private Parts
Most organizations have independent units, usually called departments.
The sales department makes the sales, the production department produces
the item, the shipping department ships it, and so on. Each department
is responsible for its own procedures and internal information. For example, the sales department has procedures for calling prospects, evaluating
the opportunity, sending sales materials, following up with current
customers, maintaining prospect information, and so on.
You could say the departments are encapsulated because the internal
information (properties) and standard operating procedures (methods)
are contained within the department. These are figuratively hidden from
other departments except through defined interfaces. Anyone who needs
the department’s procedures or information goes to that department to
ask for it.
If a shipping clerk acquires the name of a prospect, the clerk knows
better than to handle the prospect directly. Instead, the clerk collaborates
with the sales department by using the publicly accessible defining prospects
feature and sends the name to them.
The same principles are used in object-oriented applications to encapsulate
each object’s properties and methods. When an object needs to perform
a procedure that is encapsulated in another class, it does not perform
the procedure directly. Instead, it collaborates with an object belonging to
the other class to perform the procedure.
Encapsulation aids in abstraction by hiding the internal implementation
of an object within the class. You then can use an object without
understanding how its class is implemented. So the shipping clerk can
define a prospect to the sales department without knowing how the sales
department actually handles the prospect.
Inheritance: Attaining Reuse
Things that are similar still have some differences, and things that are
different often have some similarities. Reviewing the inheritance
example (from my post here), both your desk phone and your cell phone can be classified as phones. Looking at their differences, your desk phone can transfer calls, and your cell phone can take pictures.
Say you are asked to create phone emulator software. You could implement
all properties and methods for a cell phone into a cell phone class. This class would include volume and picture count properties and dial, answer, disconnect, and take picture methods. Likewise, you could implement all properties and methods for a desk phone into a desk phone class.
This class would include volume and line status properties and dial,
answer, disconnect, and transfer call methods. This duplicates the common
phone information and functionality in both classes.
You can remove the property and method redundancy and attain reuse
by using inheritance. You can remove the common phone properties and
methods from the specialized classes and put them in a higher-level phone
class. The cell phone class and desk phone class can then inherit from the
phone class, attaining all its properties and methods. This “is a” relationship
is depicted here.
If a new type of phone class, such as a Web phone, were added, objects of the new class would automatically have all the properties and methods defined for the base phone class, greatly simplifying the development of the new class.
Inheritance is a powerful tool for code reuse.
Polymorphism: Same Behavior, Different Implementation
Two or more classes can have methods that are named the same and have
the same basic purpose but different implementations. This is polymorphism.
The Text property of the .NET Framework is an example of polymorphism.
Although the basic purpose of the Text property is the same, how the property works depends on whether you are setting the Text property of a Form, a Label, or a TextBox.
In the phone example, say the implementation of the Dial method
needed to be different in the cell phone class and the desk phone class.
The cell phone class would then need its own Dial method, and a desk
phone class would need its own Dial method. You can request the Dial
method by an object from either class without knowing how either class
plans to implement that request. Both the cell phone class and the desk
phone class have the Dial method, but the implementation of that behavior
can be completely different.
Polymorphism can be implemented using inheritance. The base phone
class would retain a Dial method, but the implementation would be empty
(or would provide a default implementation). Each derived class would
then override the base class Dial method by implementing the method.
The result is that the desk phone class and cell phone class each have a
Dial method, but the implementations are different.
Polymorphism can also be implemented using an interface. In the
phone example, the Dial method would be removed from the base phone
class. An IDial interface would define the properties and methods for
handling dialing. Both the desk phone class and cell phone class implement
this interface so that they both have the same properties and methods
for dialing but different implementations. (See this link for more information on interfaces.)
The benefit of polymorphism is that you don’t need to know the
object’s class to execute the polymorphic behavior. For example, you may
have many classes in an application, each with its own save method. When
the application is saved, each object knows the class it belongs to and automatically calls the correct save routine.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
When talking about OO, the term “interface” has nothing to do with your
user interface. An interface defines a list of properties and methods that
a class can implement. But if the class implements a particular interface, it
must implement all properties and methods defined by that interface.
[To begin with an overview of OO, start here.]
For example, you can think of a phone as implementing a dial interface,
IDial. (By convention, interfaces begin with the letter I.) This interface defines Connect and MakeTone methods but does not implement them. This allows each phone to define how it connects and makes tones. Every phone that implements the IDial interface must provide an implementation
for both the connect and make tone methods.
An interface is different from a base class in that an interface defines
the list of properties and methods with no implementation. The implementation
is left to the class that implements the interface. A base class contains both the list of properties and methods and their implementation. The class that inherits from the base class does not need to provide any implementation.
In your application, you can implement interfaces provided in the
.NET Framework or define and implement your own interfaces. For
example, if you want to ensure that every MDI child form in your application
provides a standard set of methods, you could define an IMDIChild interface as follows:
In C#:
public interface IMDIChild
{
bool ProcessDelete();
bool ProcessNew();
bool ProcessSave();
}
In VB:
Public Interface IMDIChild
Function ProcessDelete() As Boolean
Function ProcessNew() As Boolean
Function ProcessSave() As Boolean
End Interface
In this example, the interface is comprised of three methods: ProcessDelete, ProcessNew, and ProcessSave. Each of these methods must be implemented in any class that implements this interface.
To implement an interface in a class, specify the interface when defining the class. For example, if the TimeSheetWin class implements the IMDIChild
interface, it would look like this:
In C#:
public class TimeSheetWin : IMDIChild
In VB:
Public Class TimeSheetWin
Implements IMDIChild
Each class that implements this interface must provide an implementation
for all three of the interface methods.
[For an example of implementing the .NET IDataErrorInfo and INotifyPropertyChanged interfaces, see this link.]
Implementing an interface allows a class to be more formal about the
behavior it promises to provide. Interfaces form a contract between the
class and the rest of the application, and the compiler enforces this contract.
If your class claims to implement an interface, all methods defined by that
interface must be implemented before the class successfully compiles.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!
In object-oriented (OO) terms, inheritance defines an “is a” relationship between two or more classes. A beagle is a dog, and a poodle is a dog, so both beagle and poodle inherit from dog. Both beagle and poodle have dog attributes and exhibit dog behaviors. A dog class in this example is called the parent class or base class, and the classes that inherit from it (beagle and poodle) are called child classes or derived classes.
[To begin with an overview of OO, start here.]
Likewise, think about your phone. All types of phones have basic
phone attributes and behaviors, such as volume, dial, answer, and disconnect.
Your desk phone may have additional, specialized behaviors, such
as transfer features. Your cell phone has amazing features such as taking
pictures and playing movies.
Even though each type of phone may have specialized behaviors, your
desk phone is a phone, and your cell phone is a phone, so both desk phone
and cell phone inherit their basic functionality from phone. Phone is the
parent (or base) class, and desk phone and cell phone are the child (or
derived) classes. You could draw this relationship as shown below.
When you inherit from a class, all the properties and methods in the
base class are available to the derived class, just as if the properties and
methods were in the derived class. So the desk phone can perform the
answer method, as can the cell phone, even though the implementation
for answer is defined in the base phone class.
Any class in the .NET Framework that is not intentionally sealed
(marked as not inheritable) can be a used as a base class. So you can inherit
the functionality of the .NET Framework classes in your application.
You can also use any class in your application as a base class and inherit
from it.
You can see inheritance in action as soon as you create your first form.
When you create a form, Visual Studio generates the following code:
In C# (in the TimeSheetWin.cs file):
public partial class TimeSheetWin: Form
In VB (in the TimeSheetWin.Designer.vb file):
Partial Class TimeSheetWin
Inherits Windows.Forms.Form
In VB, the Inherits keyword specifies that your form inherits from the .NET
Framework’s Windows.Forms.Form class. In C#, a colon separates the class name from the base class, which again is the Form class. This Form class provides all the common code required to make your form work like a Windows form.
NOTE: In VB, the designer file is, by default, hidden from view in Solution Explorer. To see the designer files for a VB project, select the project in Solution Explorer and then click on the Show All Files button in the Solution Explorer toolbar. Repeat the process to hide the files.
As you define the classes for your application, you may find that some
classes have a number of the same properties and methods but also have
some properties and methods that are different. You can extract the common
properties and methods into another class and then use that class as
a base class.
For example, you may find that each of your business object classes
(such as Employee and TimeSheet) requires a property to keep track of
the dirty state (added, updated, deleted). You can build a business object
base class that contains this property instead of adding it to every class.
Every business object class can then inherit from this base class and therefore have this property.
[For a code example of a business object base class, see this link.]
Using inheritance can minimize the amount of repeated code in your
application. Common code is written only once in the base class and is
reused by every derived class. Inheritance also makes your derived classes
easier to build and maintain, because the derived classes focus exclusively
on the features that make them unique. And if you ever need to change
that common code, you have to change it in only one place.
(Based on an except from "Doing Objects in Visual Basic 2005".)
Enjoy!