Click here to Skip to main content
12,350,134 members (26,479 online)
Click here to Skip to main content
Add your own
alternative version


92 bookmarked

Calcium: A modular application toolset leveraging PRISM – Part 3

, 21 Nov 2009 BSD
Rate this:
Please Sign up or sign in to vote.
Introducing the Calcium SDK. Calcium provides much of what one needs to rapidly build a multifaceted and sophisticated modular application. Includes a host of modules and services, and an infrastructure that is ready to use in your next application.

Calcium Logo


Learn how to create a Calcium project using the new Calcium SDK in this getting started video. Please note that clicking on the image will take you to the Calcium SDK website, where you can watch the video. I wanted to show it inline with the article, however it proved to be too difficult.

Show Video


Calcium is a WPF composite application toolset that leverages the Composite Application Library. It provides much of what one needs to rapidly build a multifaceted and sophisticated modular application.

Calcium Screen shot

Figure: Calcium Desktop Environment

In part one of this series, we explored some of Calcium’s core infrastructure, including module management, region adaptation, and the Bootstrapper. In part two we looked at the location agnostic message service, which allows a set of common dialogs to be displayed to the user from anywhere, be it server-side or client-side; the web browser module, and the Output module. Now we will examine the View Service and File Service, and discuss various changes to the codebase since writing the first article.

In the first article we saw that Calcium consists of a client application and server based WCF services, which allow interaction and communication between clients. Out of the box, Calcium comes with a host of modules and services, and an infrastructure that is ready to use in your next application.

There are many parts to Calcium, and for that reason I’ve decided to break this article up into a series of several articles.

  1. Introduction to Calcium, Module Manager.
  2. Message Service, Web Browser module, Output Module.
  3. View Service, File Service, Rebranding Calcium (this article)
  4. Text Editor Module, User Affinity Module, Communication Module
  5. TBA

In this article you will learn how to:

  • Create a Calcium project using the Calcium SDK (Video),
  • use a file service to abstract the saving and loading of data from any source,
  • and show or hide content using a type identification system.

Some of the content in this series of articles are not at an advanced level and will suite even those that are new to Prism. While others, such as the messaging system, will suite more advanced readers. Hopefully there are things here to learn for everyone.

These series of articles are, in some respects, an introduction to some areas of Prism. Although, if you are completely new to Prism, you may find yourself somewhat overloaded at times, and I would recommend taking a look at some beginner Prism articles before you tackle Calcium.

Since Last Time

Since last time there have been numerous enhancements that have been made based on feedback I have received from Calcium users. Most notably, now Calcium is able to be completely rebranded. This includes replacing or customizing the: Splash Screen, About Box, Logo, and shell Title text. I have also gone to considerable effort to create VS templates for Visual Studio 2008 and 2010, including support for both C# and VB.NET.

New Visual Studio Project Templates

I have created a set of twelve Calcium project templates. There are three core templates: Launcher project, Module project, and Web Application project, which have been ported for different environments: Visual Studio 2008 (C# and VB.NET), and Visual Studio 2010 (C# and VB.NET).

Calcium Module Project Template

The Calcium Module project is perhaps the more interesting of the set of Calcium project templates. This template generates a set of three classes providing for a Prism module. When you create a new Calcium Module the project naming convention [Namespace].[ModuleName] is used to derive the names for the various generated items. For example, if you happen to name your new project MyNamespace.Demo, then what results is:

  • a module called MyNamespace.DemoModule,
  • a view called MyNamespace.DemoView,
  • and a viewmodel called MyNamespace.DemoViewModel.

Behind the Scenes with Custom Template Parameters

To accomplish the automatic naming of project items an IWizard implementation named ModuleProjectWizard is used to insert a custom template parameter, which is then used to name our project output items. All of the Calcium SDK VS Templates make use of an IWizard, which is declared in each VS Template file like so:

<VSTemplate Version="2.0.0" Type="Project">
    Version=, Culture=Neutral, PublicKeyToken=f32f1bf552288cd5</Assembly>

The assembly containing the ModuleProjectWizard is placed in the gac during installation of the Calcium SDK. Subsequently, the ModuleProjectWizard method, which is executed when a new Calcium Module project is created, looks like this:

public void RunStarted(object automationObject, 
    Dictionary<string, string> replacementsDictionary,
    WizardRunKind runKind, object[] customParams)
  string vsVersion;
  if (!replacementsDictionary.TryGetValue("$VSVersion$", out vsVersion))
    MessageBox.Show("Problem determining Visual Studio Version. " 
        + "Please reinstall Calcium SDK.");

  if (runKind == WizardRunKind.AsNewProject
    || runKind == WizardRunKind.AsMultiProject)
  /* Here we derive a module name using the last part of the project name. 
   * For example, if the user has entered MyNamespace.TextEditor 
   * in the new project dialog, then we elect TextEditor as the module name. */
    string projectName = replacementsDictionary["$safeprojectname$"];
    string lastWord = null;
    int lastIndex = projectName.LastIndexOf('.');
    if (lastIndex != -1 && lastIndex < projectName.Length - 1)
      lastWord = projectName.Substring(lastIndex + 1);

    if (string.IsNullOrEmpty(lastWord))
      lastWord = "Custom";

    replacementsDictionary["$CustomModule$"] = lastWord;
    string installDir = RegistryUtil.GetCalciumInstallDirectory(vsVersion, "1.0");
    replacementsDictionary["$InstallDir$"] = installDir;

The RunStarted method is called by Visual Studio when the project is being created. We can see that the predefined $safeprojectname$, which is in fact the name of the new project entered by the user (with any invalid characters removed), is split according to the naming convention mentioned earlier.

We are able to locate the location of the Calcium assemblies using the registry.

If you are wondering why I chose not to place the Calcium assemblies in the gac, it is because I wish to allow for customization using the source code as well as the SDK installer. As we know, the CLR looks in the gac first when resolving assemblies, so if assemblies were placed in the gac a developer would need to update the version numbers of all assemblies and config files. If you were just thinking “I didn’t know that the CLR looks in the gac first!”, then there is another reason for avoiding that approach, and I rest my case.

During installation of the Calcium SDK, we record the install path. We also use the Visual Studio version value located in the $VSVersion$ custom parameter to identify which registry key to use. The $VSVersion$ parameter is defined in each vstemplate like so:

    <CustomParameter Name="$VSVersion$" Value="9.0"/>

So, we can see that custom parameters are able to be defined both in a vstemplate, and in code (in the IWizard implementation).

Once a custom template parameter is defined, it can be consumed by a VS Template. In order to name project items using the custom parameter the VS Module Templates include the following excerpt:


<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 


<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 
<ProjectItem ReplaceParameters="true" 

The physical TemplateModule.cs file, for example, is copied to the newly created project, and its name is transformed from $CustomModule$Module.cs to DemoModule.cs.

Resulting Project Items

We’ve looked at what happens behind the scenes; now let’s turn our attention to the actual generated output. The content of the module is shown in the following excerpt:


[Module(ModuleName = "Demo")]
public class DemoModule : ModuleBase
  /* The location where the view will be placed. */
  const string defaultRegion = RegionNames.Tools;
  /* The default view for this module. */
  DemoView view;

  public DemoModule()
    Initialized += OnInitialized;

  void OnInitialized(object sender, EventArgs e)
    view = new DemoView();
    /* The view is registered with the view service so that it appears in the 'view' menu. */
    var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
    viewService.RegisterView("Demo", obj => ShowView(defaultRegion, view, true), null, null, null);

    /* Populate the defaultRegion with the view. */
    ShowView(defaultRegion, view, false);


<[Module](ModuleName:="Demo")> _
Public Class DemoModule
Inherits ModuleBase

  Public Sub New()
    AddHandler MyBase.Initialized, New EventHandler(AddressOf Me.OnInitialized)
  End Sub

  Private Sub OnInitialized(ByVal sender As Object, ByVal e As EventArgs)
    Me.view = New DemoView
    Dim viewService As IViewService = ServiceLocatorSingleton.Instance.GetInstance(Of IViewService)()
    viewService.RegisterView("Demo", AddressOf ShowView2, Nothing, Nothing, Nothing)
  End Sub

  Private Sub ShowView2(ByVal obj As Object)
    MyBase.ShowView(defaultRegion, Me.view, True)
  End Sub

  Private Const defaultRegion As String = "Tools"
  Private view As DemoView
End Class

By default, the generated module subscribes to the base class Initialized event. When the handler (OnInitialized) is called, the module will instanciate a new view, and register it with the IViewService. We cover view registration later in this article. Once the registration is complete, the module displays the new view, in the default region, using the base class ShowView method.

The next project item that we will examine is the View. The following generated view XAML demonstrates how the DataContext of the ViewControl is set to a new instance of the DemoViewModel.


<Gui:ViewControl x:Class="CalciumWinProject1.Demo.DemoView"
    DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}">
        <Module:DemoViewModel />


The VB.NET template creates a nearly identical XAML file, apart from the x:Class attribute, which does not include a namespace prefix.

<Gui:ViewControl x:Class="DemoView"
    DataContext="{Binding ViewModel, RelativeSource={RelativeSource Self}}">
        <Module:DemoViewModel />


The generated DemoView.xaml.cs and DemoView.xaml.vb code-beside files, are absent of any presentation logic, View-ViewModel plumbing.


public class DemoViewModel : ViewModelBase
    public DemoViewModel()
        TabHeader = "Demo";


Public Class DemoViewModel
    Inherits ViewModelBase

    Public Sub New()
        MyBase.TabHeader = "Demo"
    End Sub

End Class

The resultant ViewModel derives from the ViewModelBase class, and by convention the TabHeader is set in the constructor.

Services Recap

Since the first release, the IoC infrastructure of Calcium has been overhauled. No longer are we explicit about the use of a Unity container, for this has been replaced with the Common Service Locator library, so that the IoC container can be substituted with a container of your choice. I still choose to use the Singleton pattern for service location, as I’ve found dependency injection to be troublesome when tracking down resolution failures. But feel free to use whatever approach suits you, e.g., DI via constructor injection. The ServiceLocatorSingleton wraps the Microsoft.Practices.ServiceLocation.ServiceLocator, so that only a reference to my base library is required.

The Microsoft.Practices.ServiceLocation.ServiceLocator does not provide a means to register type associations. For that I have created a set of extension methods in the ServiceLocatorExtensions class. In order to provide for registering of type associations, in an IoC container other than Unity, implement the DanielVaughan.ServiceLocationIServiceRegistrar interface and register it with the container you wish to use.

/// <summary>
/// Allows setting of type to type, or type to instance associations
/// in the service container.
/// </summary>
public interface IServiceRegistrar
    void RegisterInstance<TService>(TService service);
    void RegisterInstance(Type serviceType, object service);
    void RegisterType<TFrom, TTo>() where TTo : TFrom;
    bool IsTypeRegistered(Type type);

The ServiceLocatorSingleton will then make use of the type when registering type associations.

View Service

The IViewService implementation allows for the association and interaction of UIElements with workspace content, and provides the capability to have the visibility of a UIElement change according to the visibility or active state of a view or view-model type.

Visibility Association

One feature of the IViewService is that it allows one to hide or show any UI element in the shell, depending on the type of view that is active. When we associate a UIElement, such as a ToolBar, with a known type, if the current active view is of that type, then it will be visible, otherwise it will be hidden. We can see it in action in the Web Browser module, where we do just that: tell the service to hide the toolbar if the active view is an IWebBrowserView.

/* Add web browser toolbar. */
var toolBarProvider = new WebBrowserToolBar { Url = startUrl };
var toolBar = toolBarProvider.ToolBar;

var viewService = UnitySingleton.Container.Resolve<IViewService>();
new UIElementAdapter(toolBar), Visibility.Collapsed);

Here we have placed the toolbar in a WPF UserControl called WebBrowserToolBar so that we have designer support. We add the ToolBar, which is exposed as a public property in the provider. Note it is necessary to remove the toolbar from its parent in the custom control, as a Visual Element can only have a single parent.

We could stop at this point and the toolbar would be always present in the shell. In order to hide and show the toolbar depending on the view however, we use the IViewService instance. In particular, we use the AssociateVisibility method to associate the type IWebBrowserView, with the toolbar. The effect of this is to show the toolbar when the active view in the shell implements the IWebBrowserView interface, otherwise the toolbar is hidden.

There are a couple of things to note here. First, we specify that the hidden state will be Collapsed. This of course removes all trace of the item and allows other elements to occupy its space. It could alternatively be Hidden, which would make the item invisible, yet retain the space taken by the element. This is not rocket science.

Second, we use a UIElementAdapter to wrap the UIElement. Why? Well in order to Mock System.Windows.UIElement I thought it prudent to create a reusable wrapper and an associated interface IUIElement. You see, UIElement.Visibility is a dependency property, employing validation that prevents setting it arbitrarily. Thus we use a mockable interface and an adapter.

The following demonstrates how the IViewService is tested using the IUIElement interface.

public void ViewServiceShouldChangeVisibility()
    var uiElement = MockRepository.GenerateStub<IUIElement>();
    uiElement.Visibility = Visibility.Visible;

    var viewService = UnityResolver.UnityContainer.Resolve<IViewService>();
    viewService.AssociateVisibility(typeof(IViewDummyContent), uiElement, Visibility.Hidden);

    /* The first view to test will not implement the associated type, 
     * and should cause our the uiElement to be hidden. */
    var view = MockRepository.GenerateStub<IView>();
    /* Raise the event that indicates that the view has changed. */

    Assert.AreEqual(Visibility.Hidden, uiElement.Visibility);

    MockRepository mockRepository = new MockRepository();
    /* We create a new view, but this time it will implement 
     * the associated interface and should cause our ui element to be shown. */
    var viewWithInterface = mockRepository.DynamicMultiMock<IView>(typeof(IViewDummyContent));
    /* Raise view changed again. */

    Assert.AreEqual(Visibility.Visible, uiElement.Visibility);

View Registration

Another feature of the IViewService is the ability to register a view, so that it is displayed in the View menu of the application. The Visual Studio module template automatically wires this up for you, as shown in the following excerpt, when you select Calcium Module from the Create New Project dialog within Visual Studio.

void OnInitialized(object sender, EventArgs e)
    view = new DemoView();
    /* The view is registered with the view service so that it appears in the 'view' menu. */
    var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
    viewService.RegisterView("Demo", obj => ShowView(defaultRegion, view, true), null, null, null);

    /* Populate the defaultRegion with the view. */
    ShowView(defaultRegion, view, false);

The IViewService has two overloads for the Public Method RegisterView method. The RegisterView method is designed to accept a Func getDisplayName, which should retrieve the text that is displayed in the view menu. There is also an Action that performs the opening of the view, so that when the view menu item is clicked, or the key gesture is performed, then the Action showView will be performed.

View Menu Screenshot

Figure: Demo module automatically placed in the View menu after registration with the ViewService.

At the time of writing I am yet to implement the means to display the icon, so that serves as a placeholder for the time being. The default implementation of the IViewService is the ViewService, and the following is an excerpt from showing one of the view registration methods.

public void RegisterView(Func<object, object> getDisplayName, 
    Action<object> showView, object showViewParam, 
    KeyGesture showGesture, Func<object, object> getIcon)
	var regionManager = ServiceLocatorSingleton.Instance.GetInstance<IRegionManager>();
	IRegion viewRegion;
		viewRegion = regionManager.Regions[MenuNames.View];
	catch (KeyNotFoundException ex)
                /* TODO: Make localizable resource. */
		throw new UserMessageException(MenuNames.View
			+ " region was not found.", ex); 

	var commandParameter = new OpenViewCommandParameter(showView, showViewParam);
	var menuItem = new MenuItem
		Command = ShowViewCommand,
		Header = getDisplayName(showViewParam),
		CommandParameter = commandParameter

class OpenViewCommandParameter
	public Action<object> EventAction { get; private set; }
	public object Param { get; private set; }

	public OpenViewCommandParameter(Action<object> eventAction, object param)
		EventAction = eventAction;
		Param = param;

File Service

At first glance the IFileService interface appears to be merely an abstraction of OpenFileDialog and SaveFileDialog. It is, however, much more than that. While it does take care of displaying dialogs and handling common IO exceptions, in a platform agnostic manner. It also allows the user to select other alternative locations for files, and provides feedback to the user.

It is not the concern of the File Service to save or load files. Its only concern is in their selection. I suppose in some respects it could be called a File Selection Service. It is the task of the caller to specify a handler for saving or loading a file. This way we can have uniformity in dealing with common errors. I expect, in the future, to further abstract the dialog filter parameter, as it might be better to use a Known Association Manager for this.

We are able to be remarkably concise when using the File Service. For example, in the following excerpt we see how the TextEditorModule is able to take over the common task of presenting a dialog. It will attempt to read the file and handle any IO Exceptions, notify the user, retry if necessary, and so on.

var fileService = ServiceLocatorSingleton.Instance.GetInstance<IFileService>();

string fileNameUsed = null;
string textFromFile = null;

FileOperationResult operationResult = fileService.Open(
name =>
    textFromFile = File.ReadAllText(name);
    fileNameUsed = name;
}, FileErrorAction.InformOnly, fileFilter);

if (operationResult == FileOperationResult.Successful)
    /* Create the TextEditorView as we have the content from the file. */

The key to understanding this API is to recognize that it is the responsibility of the FileOperationHandler to record the file name used. Once we have successfully opened or saved a file, the FileOperationHandler should retain the file name that was used. The flexibility afforded to us by completely delegating the actual IO task etc., to the handler should not be underestimated. Kudos to Mike Krüger, previously of the #SharpDevelop project, from whom I first learned this technique, several years ago.

In other scenarios we are able to allow the user to select an alternate location for saving the file if the handler fails.

File Service Save Flow Chart

Diagram: File Service saves a file with error handling.

File Service Open Flow Chart

Diagram: File Service opens a file and shows dialogs automatically.

Rebranding Calcium

Previously rebranding Calcium (removing Calcium logos etc.) could only be done by modifying the source version. I have since provided for removing/replacing all trace of the Calcium branding from your application, without having to resort to getting one’s hands dirty.

Hiding the Calcium Banner Logo

Hiding the Calcium banner logo can be achieved by retrieving the IShell instance from the service locator, and setting the LogoVisible property to false, as shown in the following excerpt.

var shell = ServiceLocatorSingleton.Instance.GetInstance<IShell>(); 
shell.LogoVisible = false;

Populating the Banner Region

The Shell Banner is now a Prism region. This means you can retrieve it and populate it with UIElements.

<Border x:Name="border_Banner" Grid.Row="0" Grid.ColumnSpan="3">
<Expander x:Name="expander_Banner" IsExpanded="True" ExpandDirection="Down"                      
    Style="{DynamicResource ExpanderStyle}" 
    Visibility="{Binding BannerVisible, Converter={StaticResource booleanToVisibilityConverter}}">
<StackPanel cal:RegionManager.RegionName="{x:Static Desktop:RegionNames.Banner}" 
    <Controls:TitleBanner x:Name="titleBanner" HorizontalAlignment="Stretch" Padding="0, 0, 10, 5"
        Visibility="{Binding LogoVisible, Converter={StaticResource booleanToVisibilityConverter}}"/>

To accomplish this I have created a custom Prism Region Adapter, called PanelRegionAdapter. It is associated with the Panel type in the Bootstrapper’s ConfigureRegionAdapterMappings method, as shown in the following excerpt:

var mappings = base.ConfigureRegionAdapterMappings() 
    ?? Container.Resolve<RegionAdapterMappings>();
mappings.RegisterMapping(typeof(Panel), Container.Resolve<PanelRegionAdapter>());

The StackPanel is then populated using the region adapter.

Hiding the Entire Banner

If you wish to conserve space, and don’t wish to show the banner at all, you can hide it like so:

var mainWindow = ServiceLocatorSingleton.Instance.GetInstance<IMainWindow>(); 
mainWindow.BannerVisible = false;

Switching the About Box

The About Box can now be replaced by registering with the ServiceLocatorSingleton an IAboutBox type association.

ServiceLocatorSingleton.Instance.RegisterType<IAboutBox, MyAboutBox>();

Of course, MyAboutBox must implement the IAboutBox interface.

Changing the Window Title Text

Window title is now customizable, and also allows for string formatting. Thus we can register a localized string format once, and this will be applied whenever the Title is set. For example:

var mainWindow = ServiceLocatorSingleton.Instance.GetInstance<DanielVaughan.Gui.IMainWindow>(); 
mainWindow.Title = "project1"; 
mainWindow.TitleFormat = "{0} - My Application"; 

The way this works is that the title of the shell window is databound to its view model TabHeader property.

Title="{Binding TabHeader}"

We set the StringFormat property of the Title binding in code whenever the format changes. Here is an excerpt from the DesktopShellView class:

void SetTitleFormat(string format)
    Binding tabHeaderBinding = new Binding("TabHeader")
            Source = DataContext,
            StringFormat = format
    SetBinding(TitleProperty, tabHeaderBinding);
    ShellViewModel.TitleFormat = format;

Changing the Splash Screen Image

By using the StartupOptions, the splash screen image can be replaced with an image that represents your product.

var  starter = new AppStarter();
    = new Uri("pack://application:,,,/YourAssembly;component/Resources/Images/YourImage.jpg");

Hiding the Calcium Splash Screen Blurb

By default a piece of text is displayed in the bottom left hand corner of the splash screen. If you don’t like this, then you can remove it by, once again, using the StartupOptions of the AppStarter class, like so:

starter.StartupOptions.SplashBlurbVisible = false;

In a future release I will include a composite event that will allow a text field in the splash screen to display loading information to the user.

Changing the Web Browser Start URL

By default the Web Browser will attempt to load a default URL. To change this behaviour modify the app.config of your launcher application.

	<add key="WebBrowserStartUrl" value=""/>

Calcium for Silverlight

Looking over the codebase, you may discover #if Silverlight preprocessor directives in various places. Yes, I am in the process of porting Calcium to Silverlight. It is my aim that most of the code in Calcium can target both the Desktop and Silverlight CLRs. So, a Module, or a ViewModel in a Calcium application can be used to target both Silverlight and WPF. Reaching this goal has been hindered by the absence of a Menu and Toolbar controls in Silverlight. I have had requests to provide for the Office 2007 Ribbon control in the WPF version, and serendipitously discovered that there is also a rather nice Silverlight implementation. So, depending on compatibility of the licensing of the Ribbon I may provide support for it on both platforms. We’ll see.


In this article we have looked at some of the changes that have taken place in the Calcium project since the first release, a number of months ago. We have seen how using Calcium has become even easier with the use of Custom Project Items, and we have explored behind the scenes of creating VS Templates for Calcium. We took a look at how the Service Location infrastructure has changed, and examined how to use the IViewService to hide and show UIElements. We also saw how the IFileService takes care of displaying dialogs and handling common IO exceptions, in a platform agnostic manner.

In the next article we will move to the Text Editor module and we’ll observe how the TextEditorViewModel is able to provision for file changes using a content interface approach. We’ll also look at some of the other services including the Communication service and its related module.

As development of Calcium continues, I know we still have a lot to cover, and I hope you will join me for our next installment.

If you have a feature request, or discover a bug, please place it on the Calcium Codeplex Issue Tracker, or vote for the issue if it already exists.

I hope you find this project useful. If so, then I'd appreciate it if you would rate it and/or leave feedback below. This will help me to make my next article better.


November 2009

  • First publication.


This article, along with any associated source code and files, is licensed under The BSD License


About the Author

Daniel Vaughan
President Outcoder
Switzerland Switzerland
Daniel Vaughan is a Microsoft MVP and co-founder of Outcoder, a Swiss software and consulting company dedicated to creating best-of-breed user experiences and leading-edge back-end solutions, using the Microsoft stack of technologies--in particular WPF, WinRT, Windows Phone, and also Xamarin.Forms.

Daniel is the author of Windows Phone 8 Unleashed and Windows Phone 7.5 Unleashed, both published by SAMS.

Daniel is the developer behind several acclaimed Windows Phone apps including Surfy, Intellicam, and Splashbox; and is the creator of a number of popular open-source projects including Calcium SDK, and Clog.

Would you like Daniel to bring value to your organisation? Please contact

Daniel's Blog | MVP profile | Follow on Twitter

Windows 10 Experts
Surfy Industrial Browser

You may also be interested in...

Comments and Discussions

GeneralMy vote of 5 Pin
blackr2d6-Jul-10 3:00
memberblackr2d6-Jul-10 3:00 
GeneralRe: My vote of 5 Pin
Daniel Vaughan28-Jan-11 11:59
mvpDaniel Vaughan28-Jan-11 11:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160621.1 | Last Updated 21 Nov 2009
Article Copyright 2009 by Daniel Vaughan
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid