Thursday, December 13, 2007

Serialization

I've read and written to so many text files over the past several years, but never once had a need to write to a binary file. I've always wondered how it was done and what situations would require me to write to a binary file, but I never took the time to investigate it further. Primarily due to some time constraint or another, but now I've taken the plunge, and folks, the water is not nearly as deep or cold as I imagined it to be.

I write a lot of database driven code that provides me with ample means to store data in a data table. These programs often involve me creating a class file around a specific data structure using properties to get and set values for the structure, intantiating an instance of these classes as objects and then writing them to the database.

I'm going to go through a simple example here of just such an object and then I'm going to create a class that defines a collection of these objects and write that collection to a binary file for later use. To begin, let's create a project in Visual Studio 2005 and define a form to allow users to enter, save and retrieve saved names.

I've included a sample C# project with this post and it is available by request.

Here's the form that I created:



Pretty basic stuff... I've included two text boxes to allow users to enter a name and title for a person. I've also included a ListView control to contain all of the names in the list of persons, along with buttons to add names to the list, save the list, open a saved list and exit the application. The ListView control contains two columns (name and title) and its View property is set to Details.

Next, I defined a class file to represent each person as an object with properties defined for both name and title:


using System;
using System.Collections.Generic;
using System.Text;
namespace
SerializationExample1
{
[Serializable]
class person
{
private string name;
private
string
title;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
public
string
Title
{
get
{
return title;
}
set
{
title = value;
}
}
}
}

Notice the [Serializable] attribute at the start of the class which allows this class to be serialized. When you do this, the class is no longer able to be inherited, so design your projects with this in mind.

Next, we add some basic code to the form class to handle the addition of names to the list. First, I create a generic list of type person called People. This list will maintain all of the instances of the person class and will be used to populate the ListView control:


private List<person> People = new List<person>();

Next, I create a code block for the Add button's click event and create the code to add a new instance of the person class to the list:


private void buttonAdd_Click(object sender, EventArgs e)
{
person newPerson = new person();
newPerson.Name = textName.Text;
newPerson.Title = textTitle.Text;
People.Add(newPerson);

// create a new listview item and add it to the control:
ListViewItem lvi = new ListViewItem(newPerson.Name);
lvi.SubItems.Add(newPerson.Title);
listViewNames.Items.Add(lvi);
}

Now, once a few names have been added, I want to be able to save the list of names and titles to a binary file. To do this, I define a new code block for the Save button's click event and add my serialization code:

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
...
private void buttonSave_Click(object sender, EventArgs e)
{
string filename = "c:\\serializedList.ser";
Stream s = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
BinaryFormatter b = new BinaryFormatter();
b.Serialize(s, People);
s.Close();
}

Notice the using statements at the beginning. These are required to perform serialization tasks.

This code block will create a binary file called 'serializedList.ser' to the root directory of drive c:\\. If you want to get fancy with this you can create a SaveFileDialog box and prompt for the name and location of the file. You could also create a bin directory in the application folder to house these files. This file name and location was chosen for the sake of simplicity.

So, next we need to define a code block to respond to the Open button's click event which will load the saved data and populate the ListView control with the data contained in the binary file:


private void buttonOpen_Click(object sender, EventArgs e)
{
Stream s = null;
string filename = "c:\\serializedList.ser";
List<person> LoadedPeople = null;
bool loaded = false;
try
{
IFormatter formatter = new BinaryFormatter();
s = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None);
LoadedPeople = (List<person>)formatter.Deserialize(s);
loaded = true;
}
catch (System.Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
s.Close();
}
if (loaded)
{
listViewNames.Items.Clear();
for (int i = 0; i < color="#66cccc">person
newPerson = LoadedPeople[i];
ListViewItem lvi = new ListViewItem(newPerson.Name);
lvi.SubItems.Add(newPerson.Title);
listViewNames.Items.Add(lvi);
}
}
}

Here is a look at a populated form:






Sunday, September 16, 2007

Dermine Windows OS Version

I recently had the need to determine what OS the user is running on the client PC.

After performing a quick Google search, I arrived at the answer to my quest. Here are the results:

It turns out that the System.Environment namespace houses a lot of useful items. One of them is the OSVersion class. The OSVersion.Version property contains the version number which is what I wanted to determine which OS the user host computer is running. The other properties referenced are merely eye-candy.

Using Windows XP:


Using Windows Vista:

Notice how the version number changes from Windows XP to Windows Vista.

To obtain the data shown above, the Environment.OSVersion class is used:


string osVStr = Environment.OSVersion.VersionString;
labelOSVStr.Text = osVStr;
string osVPlatform = Environment.OSVersion.Platform.ToString();
labelOSVPlatform.Text = osVPlatform;
string osVSvcPack = Environment.OSVersion.ServicePack.ToString();
labelOSVSvcPack.Text = osVServicePack;
string osV = Environment.OSVersion.Version.ToString();
labelOSVersion.Text = osV;

From here, it is a simple step to compare version numbers against a desired level, verify that service packs have been applied, etc.

GUIDs and C#

I've started using GUIDs (pronounced "goo-id") which are globally unique 128-bit integers (displayed as HEX). I created a small program to generate and display a GUID using the System.Guid.NewGuid() method in C#.



Here's the opening screen:



After clicking the 'Create GUID' button, the new GUID is displayed:

The code used to generate the GUID is as follows:


Guid myGUID = System.Guid.NewGuid();
labelGUID.Text = myGUID.ToString();



I'm starting to prefer this method of creating ID values for data tables more than the indexing values I currently use. This should make maintaining disconnected databases on handheld devices much simpler and more effective.

Intermec CN3 With Imager

I just returned from the Interemec DevCon in Boston and wanted to put together a Windows Mobile application that uses some of the things I learned while there.

First, the CN3 is a very capable Windows Mobile device manufactured by a company called Intermec http://www.intermec.com/. It has hardware specific to Intermec devices, such as the bar code reader (Imager) and uses the Intermec Development Libraries (IDL).

So, I created the application and ran it. Here's a screenshot:





Next, I modified the program and added a list of images:



From here, I see this being used to document labels that cannot be scanned, possibly damaged goods reporting, etc.

There are other options for obtaining pictures for reporting damaged goods, such as using a cell phone or digital camera, in addition to the the CN3. The CN3 also comes with an optional digital camera, but a CN3 cannot have both an Imager and a digital camera. At DevCon, this seemed to be the biggest complaint with the CN3.

The significant code sections for this application are as follows:




private Imager myImager;

...

myImager = new Imager(pbxImager, Imager.PictureResolutionSize.Full);
myImager.VideoRunning = true;
myImager.OnScreenCaption = "Imager Example";
myImager.OnScreenCaptionPosition = Imager.OnscreenCaptionPos.LowerLeft;


and then to capture the images:

ImageCount++;
string imageName = "\\JPGImager_" + ImageCount.ToString() + ".jpg";
myImager.SnapShot(imageName, Imager.PictureResolutionSize.Full, Imager.FileModeType.JPG);
imageList1.Images.Add(new Bitmap("\\JPGImager_" + ImageCount.ToString() + ".jpg"));
listView1.LargeImageList = imageList1;
listView1.View = View.LargeIcon;
ListViewItem lvi = new ListViewItem(ImageCount.ToString());
lvi.ImageIndex = ImageCount - 1;
listView1.Items.Add(lvi);

Connecting to SQL 2005

Well, I've noticed a few things since converting my desktop to Vista and using SQL Server 2005...

First thing I noticed is that SQL 2005 does not like remote TCP connections. Instead, use Named Pipes by prefixing the server name/ip address with the prefix 'np:'.

Note: This does not appear to work with SQL 2000, so I have created an app setting in the App.Config file to set the database server type:



<configuration>
<appsettings>
<add value="localhost" key="QueueServerName">
<add value="localhost" key="CacheServerName">
<add value="SQL2005" key="DBType">
</appsettings>
</configuration>


Then, in the code, I use the ConfigurationManager as follows:

string dbType = ConfigurationManager.AppSettings["DBType"].ToString();