Av rating:
Total votes: 18
Total comments: 6


John Papa
Using Silverlight to Build a Guitar Chord Calculator
12 March 2009

Silverlight is so versatile that it is easy  to move beyond conventional browser-based applications and  even write games. John Papa celebrates the launch of his new book by bringing  you a simple Silverlight Guitar Chord Calculator to demonstrate features of Silverlight such as Data binding, Media elements, Resources, Transforms, Visual states and Dynamically generated controls. He has placed the complete source on Codeplex, so that you can enhance it if you feel the urge.

One of the advantages of using Silverlight is that it can be used to build any type of application, including  games. As well as spending a lot of time building varying types of business applications and writing a book that focuses on building data driven applications, I’ve also spent time building less traditional and more fun programs with Silverlight. Regardless of the type of application and its requirements, there are some key aspects of Silverlight development that apply. This article explains how to apply some common and widely-used aspects of Silverlight to build a guitar chord finder.

The program allows the user to choose from an assortment of chords and then display on a fret board how the user could places their fingers to play the chord. The application can display and play the main major and minor chords, however it can be modified to also include other chords including suspended, 7ths, and others. (The source code for the application is posted on CodePlex at the following link. I encourage the community to grab the code and contribute to it. )

Source: http://silverlightchordcalc.codeplex.com
Demo: http://johnpapa.net/demos/chordcalc/demo.html

The sample application uses mathematical calculations to determine how to play each chord. As any guitar player knows, there are many ways to play each chord and using the mathematical calculations may not yield the most comfortable nor common finger positioning for the chord. I added some adjustments to the calculations to try to mediate this, but of course nothing is as good as purely pre-determining the finger positions for each chord.

 In a future version of this tool I will forgo the calculations and display multiple chord variations for all of the chords. But for the purpose of this article, it was more fun to show that the calculations can be used.

The application uses a CoolMenu control found on CodePlex in the Silverlight Contrib project (which is a great set of community contributions). The sample application also takes advantages of several key ingredients in a Silverlight application including the following, amongst others:

  • Data binding
  • Media elements
  • Style resources
  • Control Templates
  • File resources
  • Transformations
  • Visual states
  • Dynamically generated controls

Demo Time

I’ll start by demonstrating the application pointing out what it does and how it can be interacted with by a user. Along the way I’ll make a reference to the different elements used to create the application, which will be explained following the demo.

Fret Board

Figure 1 - Guitar Chord Calculator

The sample application is shown in Figure 1 displaying the G major chord. The sample has a fret board on the top that displays the chord fingering. The blue circles indicate where a finger should hold a string. The note that will be played is noted inside of the blue circle. If the blue circle is shown on the nut at the far left, this indicates that the string is open.

Visual Aspects and Dynamic Creation

The blue circles on the fret board are actually button controls whose control templates have been replaced. Instead of displaying a standard button, the control templates contain an ellipse with a gradient background and text content. The buttons are created in .NET code and a style resource is applied to each button before being placed on the appropriate spot on the fret board. Each button also has a visual state where the button grows slightly during a mouseover event and shrinks slightly when the button is clicked. Every audio file is added as a resource to the project. Some transformations are used to skew the viewing angle of the fret board.

CoolMenu and Data Binding

Below the fret board, a list of the notes is displayed in a CoolMenu control from the Silverlight Contrib library on CodePlex (similar to a fish eye control). There is also a list of the types of chords (in this case Major or Minor) displayed in another CoolMenu control.  Each of these controls is data bound to a List of notes and of chord types, respectively. The user can select the note and the chord type using the 2 CoolMenu controls. When the selection has been made, data binding is used to display the name of the chord at the bottom of the screen and the chord calculation logic executes to build the appropriate chord on the fret board. Data binding is also used to bind the appropriate chord’s audio file to a media element so the chord can be played.

Audio

There is an accompanying audio file for all 6 strings and for each fret and an open string for a total of 36 individual notes. If the user clicks a blue circle button on the fret board, the note will be played for that specific string and fret through a MediaElement control.

When the user selects a note and chord type, chord’s corresponding audio file is data bound to a MediaElement control. When the user clicks the musical note button, the audio file is played. There is an audio file for each of the notes and chord type combinations, for a total of 24 additional audio files.

When added together, the combined size of the audio files can become large. This is increases the size of the Silverlight XAP file and can cause the load time of the Silverlight application to slow down. One way to combat this is to use a tool to compress the audio files. The files in this sample project are compressed, however keep in mind that this compression often causes quality loss in the audio file.

Transforms and User Controls

As you can see, there are many aspects that work in concert to make the application. Now that I’ve shown what that application does and pointed out its parts, I’ll dive into how each part works within the application. The fret board is a Grid panel (named fretBoardGrid) with 6 rows and 5 columns. The fretBoardGrid is skewed slightly to give it a bit more visual perspective using the following transformation. This is most easily set by selecting the Grid in Expression Blend and setting the SkewTransform property’s X angle to -5, as shown in Figure 2.

...

  Grid.RenderTransform>

      <TransformGroup>

            <SkewTransform AngleX="-5"/>

      </TransformGroup>

</Grid.RenderTransform>;

...

Figure 2 - Skew Transform for the Fret Board

The guitar strings are represented by a user control named GuitarString and the fret bars are represented by a user control named Fret. Both of these user controls are contained in the controls folder in the Silverlight project. User controls are ideal for situations where you want to re-use the same control. This works well for the guitar string and fret bars since they are each used multiple times. The GuitarString control uses a Path to create the appearance of a guitar string. The Path has a stroke thickness of 5 and is colored, as shown below.

<Path Stretch="Fill" Stroke="#FFFAE18D" Data="M22,133 L648,133"

StrokeThickness=="5" Fill="#99FAE18D"/>

An instance of the GuitarString control is placed in each of the rows of the fretBoardGrid. Each string is the same width, but on a guitar each string gets progressively smaller from the low E to the high E string. One way to make the strings gets smaller is to use a scale transform on each string. For example, the following code shows a ScaleTransform that changes the scale of the Y for the guitar string to 10% of its initial state. The other strings all use slightly less of a scale to create the appearance of the strings.

<FretBoard:GuitarString Grid.Row="0" Grid.ColumnSpan="5"

RenderTransformOrigin="0.5,0.5" Margin="0,11.5,0,11.5" >

      <FretBoard:GuitarString.RenderTransform>

            <TransformGroup>

                  <ScaleTransform ScaleY=="0.1"/>

            </TransformGroup>

      </FretBoard:GuitarString.RenderTransform>

</FretBoard:GuitarString>

The fret bars are represented by the Fret user control. The Fret control contains a vertical gray rectangle, represented by a path. (A Rectangle control could also have been used, if desired. Either control is fine in this case.)

<Path Fill="#FFB9BFC8" Stretch="Fill" Stroke="#E5B9BFC8"

Data="M132,22 L132,210" StrokeThickness="5" Margin="0,-3,0,-3" />

The same principles that are used to display the GuitarString controls are also applied to the Fret controls. The first column of the fretBoardGrid needs to show 2 Fret control. The first fret will be a little thicker than the others as it will represent the nut of the guitar. The thickness is adjusted using a ScaleTransform to double its size (see the code sample below). The second Fret control will be aligned to the far right of the column to represent the first fret on the guitar. The subsequent Fret controls are placed in the rest of the columns, 1 per column and aligned to the right.

Example 1 – Laying the frets on the neck of the guitarr

<FretBoard:Fret Grid.RowSpan="6" Grid.Column="0"

HorizontalAlignment="Left" RenderTransformOrigin="0.5,0.5">

      <FretBoard:Fret.RenderTransform>

            <TransformGroup>

                  <ScaleTransform ScaleX="2"/>

            </TransformGroup>

      </FretBoard:Fret.RenderTransform>

</FretBoard:Fret>

<FretBoard:Fret Margin="0,0,0,0" Grid.RowSpan="6" Grid.Column="0"

HorizontalAlignment="Right"/>

<FretBoard:Fret Margin="0,0,0,0" Grid.RowSpan="6" Grid.Column="1"

HorizontalAlignment="Right"/>

<FretBoard:Fret Margin="0,0,0,0" Grid.RowSpan="6" Grid.Column="2"

HorizontalAlignment="Right"/>

<FretBoard:Fret Margin="0,0,0,0" Grid.RowSpan="6" Grid.Column="3"

HorizontalAlignment="Right"/>

<FretBoard:Fret Margin="0,0,0,0" Grid.RowSpan="6" Grid.Column="4"

HorizontalAlignment="Right"/>

All of this and the coloring and shading helps make the fret board give the appearance of a guitar fret board, as shown in Figure 3.

Figure 3 – Final appearance of the fret board

Data Bound Menus

Two CoolMenu controls are displayed below the fret board to allow the user to select the chord to display and play. The CoolMenu is a control included as part of the Silverlight Contrib on CodePlex. It is based on the ItemsControl, which is ideal for binding and displaying a list of items. The XAML for the first CoolMenu (named noteMenu) is shown in Example 2. This control must be downloaded from CodePlex and referenced by the Silverlight project before it can be added to a user control. This will add the namespace reference in the UserControl tag in the XAML, as follows:

xmlns:sc="clr-namespace:SilverlightContrib.Controls;assembly=SilverlightContrib.Controls"

Figure 4 – The Note and Chord menuss

The noteMenu CoolMenu is simple to set up. The code shown in Example 2 shows some basic layout settings for the menu and that the ItemsSource property is set to use the data binding via the DataContext. Since this control inherits from the ItemsControl, it can also use a DataTemplate to define the items in the CoolMenu. In this case each item is represented simply by an Image, which gets its source form data binding.

Example 2 – Designing the CoolMenu controls

<sc:CoolMenu HorizontalAlignment="Center" Grid.Row="1"

x:Name="noteMenu" ItemsSource="{Binding}" Height="70"

VerticalAlignment="Center">

      <sc:CoolMenu.ItemTemplate>

            <DataTemplate>

                  <Image Source="{Binding Mode=OneWay, Path=ImageSource}";

Height="36" Width="36" />

            </DataTemplate>

      </sc:CoolMenu.ItemTemplate>

</sc:CoolMenu>

The noteMenu is bound to a List<Note> classes. The Note class represents the aspects of a single note and includes the name of the note and the image file associated with the note. The NoteData class (shown in Example 3) stores a List<Note>: 1 for each of the 12 notes.  Each note image is stored in an images folder in the Silverlight project and has its Build Action set to Resource, as shown in Figure 5. Once the NoteData class is created it can be bound to the noteMenu as shown below:

noteData = new NoteData(); // Stores the basic notes (fixed data)

noteMenu.DataContext = _noteData.NoteList;

This list of data is static so there is no need to use the INotifyPropertyChanged interface nor the INotifyCollectionChanged interface. These interfaces are ideal when the contents of a list or the properties of an object might change, and the changes need to be displayed in the UI. For the noteMenu and the chordTypeMenu, this is not required since these lists do not change.

Example 3 – Defining the List<Note> to bind to the noteMenu

public class NoteData

{

    public List<Note> NoteList { get; set; }

    private const string SOURCE_ROOT = "GuitarChordFinder;component/images";;

 

    public NoteData()

    {

        NoteList = new List<Note>

           {

               new Note{Code = "A", ImageSource = SOURCE_ROOT + "/A.png"},

               new Note{Code = "A#", ImageSource = SOURCE_ROOT + "/Asharp.png"},

               new Note{Code = "B", ImageSource = SOURCE_ROOT + "/B.png"},

               new Note{Code = "C", ImageSource = SOURCE_ROOT + "/C.png"},

               new Note{Code = "C#", ImageSource = SOURCE_ROOT + "/Csharp.png"},

               new Note{Code = "D", ImageSource = SOURCE_ROOT + "/D.png"},

               new Note{Code = "D#", ImageSource = SOURCE_ROOT + "/Dsharp.png"},

               new Note{Code = "E", ImageSource = SOURCE_ROOT + "/E.png"},

               new Note{Code = "F", ImageSource = SOURCE_ROOT + "/F.png"},

               new Note{Code = "F#", ImageSource = SOURCE_ROOT + "/Fsharp.png"},

               new Note{Code = "G", ImageSource = SOURCE_ROOT + "/G.png"},

               new Note{Code = "G#", ImageSource = SOURCE_ROOT + "/Gsharp.png"}

           };

    }

Figure 5 – Making the image file a resource

The chordTypeMenu follows the same design and pattern as the noteMenu. It uses a CoolMenu control and binds to a List<ChordType> through the ChordTypeData class. You can refer to the full source code for the exact implementation, as this is set up the same way the noteMenu is set up.

Chord Bindings

When a user selects a note or a chord type, a corresponding event handler fires for each of the CoolMenu controls respectively (as shown in Example 4). The selected item (note or chord type) is retrieved from menu and set to the field _currentChord, which is an instance of the ChordAudio class. The _currentChord contains a property for the Note, the ChordType and the audio file location for the currently selected chord. The ChordAudio class implements the INotifyPropertyChanged interface and fires the ProeprtyChanged event whenever any of the properties are updated. This tells the UI to get fresh values from the class instance whenever the audio file, note or chord type changes. The ShowChord method performs the calculations that determine what notes should be played to achieve the chord.

Example 4 – Setting the Chord’s Binding in Event Handlers

private void chordtypeMenu_MenuItemClicked(object sender, SelectedMenuItemArgs e)

{

    ChordType selectedChordType = e.Item.DataContext as ChordType;

    _currentChord.ChordType = selectedChordType;pe;

    ShowChord();();

}

 

private void noteMenu_MenuItemClicked(object sender, SelectedMenuItemArgs e)

{

    Note selectedNote = e.Item.DataContext as Note;

    _currentChord.Note = selectedNote;

    ShowChord();

}

The _currentChord field is bound to the SelectedChord StackPanel which displays the selected chord’s name and binds the audio file to the chordPlayer MediaElement. Using data binding allows the UI to update itself and be prepared to play the currently selected chord immediately after the user selects a different note or chord type.

Example 5 – Binding the Selected Chord

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"

      VerticalAlignment="Center" x:Name="SelectedChordPanel" Grid.Row="4">

      <TextBlock Text="{Binding Mode=OneWay, Path=Note.Code}"

      FontSize="16" Foreground="#FFE7E7E7" HorizontalAlignment="Center"

      Margin="0,0,4,0" VerticalAlignment="Center"/>

      <TextBlock Text="{Binding Mode=OneWay, Path=ChordType.Code}"

FontSize="16" Foreground="#FFE7E7E7" HorizontalAlignment="Center"

Margin="4,0,0,0" VerticalAlignment="Center"/>

      <MediaElement HorizontalAlignment="Left" Volume="1" AutoPlay="False"

x:Name="chordPlayer" Grid.Row="1" Grid.RowSpan="2"

Source="{Binding Mode=OneWay, Path=AudioFileSource}"/>

</StackPanel>

Audio Media Elements

The chordPlayer MediaElement shown in Example 5 is a control with no visible interface. It will play the audio file based on its binding, which in this case is one of the mp3’s associated with each of the chords. Each audio file is stored in the Silverlight project in the sounds folder. Like the note images, the audio files also have their Build Action set to Resource, so they can be used as a resource in the project. The chordPlayer has its Volume property set to 1 (but of course this can be adjusted by the user by perhaps a slider control). The Source of the chordPlayer is set to use data binding to get the audio file’s location. The AutoPlay property is set to an initial state of False, so the audio file will not play automatically when the audio file is bound to the MediaElement. The application should not play the chord automatically, instead it should only play when the user clicks the btnPlayChord button (the square blue musical note).

The event handler for the btnPlayChord first checks if the audio file is set. Then it turns on AutoPlay, sets the position of the audio file to the beginning so it will play from the beginning, and the it plays the audio. The AutoPlay property is then set to false again, so when the bindings change later the audio file will not automatically play. The code for this is shown below.

if (_currentChord.AudioFile.Length == 0) return;

    chordPlayer.AutoPlay = true;

    chordPlayer.Position = TimeSpan.FromSeconds(0);

    chordPlayer.Play();

    chordPlayer.AutoPlay = false;

Chord Calculations

The last aspect of the sample demonstrates how to dynamically generate the button controls on the fret board to show the user where to put their fingers to create a specific chord. The logic for displaying the chord is controlled by the ShowChord method. First the logic must determine where to put the “finger spots” (the places the user must put their fingers).

As mentioned before, this sample calculates the finger spots instead of using pre determined locations. The logic for this calculation can be seen in the source code and is based on standard musical theory, which is outside the scope of this article.

The ShowChord method fills a chart (_scaleInfoChart) containing the list of notes in a scale starting with the note for the chord. For example, if the G major chord is selected, the note chart would start with G and continue with the rest of the scale like this: G G# A A# B C C# D D# E F F#.

­Once the scale chart has been created, the GetChordNotes method determines which notes are needed to create the selected chord. It uses static data based on the scale to determine which notes in the scale are needed (again based on music theory)­­­. The notes for the chord are returned to a list. Then in the FillNoteMappingList method (shown in Example 6), the strings and frets of the guitar are then searched to find the notes required to produce the chord.

Example 6 – Mapping the Notes to the Fret Board

private void FillNoteMappingList(List<string> notesToFind)

{

    for (int stringNum = 0;

stringNum < _stringInfoChartData.StringNotesList.Count;

stringNum++) // loop 0 - 5

    {

        //Loop through the frets on the string and create the mapping

        var stringInfo = _stringInfoChartData.StringNotesList[stringNum];

        for (int fret = 0;

fret < stringInfo.FretNoteList.Count;

fret++)

        {

            var fretNote =

_stringInfoChartData.StringNotesList[stringNum].FretNoteList[fret];

            if (notesToFind.Contains(fretNote.Note.Code))

            {

                // remember this string, its fret #, and note, then get out.

                _noteMappingList.Add(

                    new NoteMapping

                        {

                            StringNumber = stringInfo.StringNumber,

                            FretNumber = fretNote.FretNumber,

                            NoteCode = fretNote.Note.Code,

                            AudioFile = stringInfo.FretNoteList.Where

(f => f.FretNumber == fret).FirstOrDefault().AudioFile

                        }););

                break;

            }

        }

    }

}

First, the FillNoteMappingList method loops through each string of the guitar. It grabs the information about each guitar string such as string number and the notes for that string when played open and when played on all of the frets. This information will be used to see if the notes on that string match any of the notes we are looking for in the chord. The method then loops through each fret (starting with the open string) in search of the matching notes. When a match is found, an instance of the NoteMapping class is created and it’s the string number, fret number, the note and the audio file for that note are stored in the NoteMapping instance. This process continues for each string on the guitar.

I added a few additional methods after this process that eliminate some of these mappings based on logic to make them more reasonable to play (for example, I limit the chords to requiring 4 fingers). This logic is imperfect, and can certainly be expanded.

Dynamically Generating Controls

Once the note mappings have been determined, the buttons must be placed on the fret board. The DisplayNoteMapping method handles this job. It loops through the mappings and creates a new Button control (shown in Example 7). The new Button is created using the style resource (FingerButtonStyle) that uses the control template that replaces the standard button with a blue circle. The button does not have a Row property but it does get an attached property for this since it is contained within a Grid. The code must set the Grid.Row property for the button to make the button show up on the proper string. The attached property is set using the SetValue method and passing in the property and the value.

Example 7 – Generating and Display the Chord Buttons

foreach (var item in _noteMappingList)

{

    var fingerSpot = new Button {

Style = App.Current.Resources["FingerButtonStyle"] as Style,

Content = item.NoteCode };

    fingerSpot.SetValue(Grid.RowProperty, item.StringNumber - 1);

    if (item.FretNumber == 0)

    {

        fingerSpot.SetValue(Grid.ColumnProperty, item.FretNumber);

        fingerSpot.HorizontalAlignment = HorizontalAlignment.Left;t;

        fingerSpot.Margin = new Thickness(-12, 0, 0, 0);

    }

    else

    {

        fingerSpot.SetValue(Grid.ColumnProperty, item.FretNumber - 1);

    }

    _fingerSpotList.Add(fingerSpot);

    fretBoardGrid.Children.Add(fingerSpot);

 

    // Use mp3's for all strings and fret positions.

    // Play them when a button is clicked.

    var audioFile = item.AudioFile;

    fingerSpot.Click += ((sender1, e1) => PlayNote(audioFile));

}

Now that the string has been set (via the row) the fret must be set (via the column). If the string is not played open, then the code sets the attached Grid.Column property using the SetValue. If the string is played open, the button is shifted to the left a bit to make it appear centered on the nut of the guitar. Once the button is created, it is added to the Grid’s Children collection. This is necessary to make it display within the Grid.

When the new button is clicked, it should play the audio file that plays the individual note. This requires that an event handler must be assigned to the Click event for each new button. The handler calls the PlayNote method which accepts the name of the audio file to play. The DisplayNoteMapping method uses a lambda expression to set the handler.

Summary

This application demonstrates several aspects of creating visual effects, transforms, style resources, file resources, media elements, overriding control templates, data binding, and dynamically generating and displaying controls in code. When all of these features are combined the result can be a compelling application using Silverlight through Expression Blend and Visual Studio.



This article has been viewed 7155 times.
John Papa

Author profile: John Papa

John Papa, Senior .NET Consultant at ASPSOFT, is a Microsoft MVP [C#], MCSD.NET, and an author of several data access technology books. John has over 10 years' experience in developing Microsoft technologies as an architect, consultant, and a trainer. He is the author of the Data Points column in MSDN Magazine and can often be found speaking at user groups, MSDN Web Casts, and industry conferences such as VSLive and DevConnections. John is a baseball fanatic who spends most of his summer nights rooting for the Yankees with his family and his faithful dog, Kadi. You can contact John at johnpapa.net.
His new book Data-Driven Services with Silverlight 2 has recently been published.

Search for other articles by John Papa

Rate this article:   Avg rating: from a total of 18 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Error
Posted by: Anonymous (not signed in)
Posted on: Thursday, March 19, 2009 at 9:35 AM
Message: Failed to load the application. It was built with an obsolete version of Silverlight.

Subject: Silverlight 2
Posted by: John Papa (not signed in)
Posted on: Saturday, March 21, 2009 at 1:45 AM
Message: RE: Anonymous Error ... The application uses the release bits of Silverlight 2. If you had a problem loading it, please feel free to contact me.

Subject: hg
Posted by: Anonymous (not signed in)
Posted on: Monday, March 23, 2009 at 1:03 AM
Message: think you

Subject: Wierd
Posted by: Anonymous (not signed in)
Posted on: Wednesday, March 25, 2009 at 8:39 AM
Message: It loaded fine for me in Firefox but not in IE. Sort of interesting!

Subject: loading
Posted by: John Papa (not signed in)
Posted on: Wednesday, March 25, 2009 at 9:18 PM
Message: RE: "Wierd"

If you have problems loading it, please email me ( you can reach me through my web site's contact page ).

The Silverlight team is great at looking into issues so they can make it a better experience. I can relay any problems to them, if I can not solve them.

Subject: Nice Job
Posted by: cliffeby (view profile)
Posted on: Tuesday, December 22, 2009 at 10:37 PM
Message: Great to see some real Silverlight that isn't a Northwind or AdventureWorks Customer grid. Thanks for the contribution.

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




Has .NET Reflector Saved Your Bacon?
 We think Reflector is a fantastic tool, and we know you do too. We'd love to hear about the times... Read more...

The Managed Heap
 Because Red-Gate's .NET team works closely with the users of their products in order to try to fit the... Read more...

Using Three Flavors of LINQ To Populate a TreeView
 LINQ is a valuable technology. LINQ to XML, LINQ to Objects and LINQ to XSD, in particular, can save... Read more...

How to build a Query Template Explorer
 Having introduced his cross-platform Query Template solution, Michael now gives us the technical... Read more...

How to Create Event Receivers for Windows SharePoint Services 3.0
 You'll be surprised how often that you'll use event receivers instead of Workflow in order to implement... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk