Click here to Skip to main content
Click here to Skip to main content

Introduction to Composite WPF (CAL, Prism): Part 1

, 12 Jun 2009
Rate this:
Please Sign up or sign in to vote.
An article showing an extremely simple implementation of CompositeWPF.

CALDemoSS.jpg

Introduction

If you're a WPF application developer, you've probably heard about CAL by now. It seems to have a number of names that people know it by, CompositeWPF, Composite Application Library, Composite Application Guidance (CAG), and Prism. Frankly, it's all a bit mad; from here on in, it's called CAL. Unlike the growing issue of loads of different techs that essentially do the same thing, here we have a single tech with loads of different names!

Anyway, I have now been involved in two large Composite WPF applications. One for my day-job employer, a smart desktop client application using WPF/ WCF / CAL, and one for my own project. However, from my own experience and from talking to other developers, I think Microsoft sometimes do themselves a real disservice. CAL is awesome, and the documentation is very good and getting better, but the StockTraderRI is just a bit too much to take in at first. There are some excellent things people are doing with CAL (Daniel Vaughan's Calcium, for one), but they aren't really a strip it down to basics take on things; they are complex frameworks in themselves, so I thought I'd write this really cut down demo app.

I'll dispense with the long waffly explanation of what it is, suffice to say that primarily, it is a library incorporating the following feature set:

  • Modularity
  • Dependency Injection Container (IoC)
  • ObjectBuilder2
  • Event Aggregation
  • Runtime Discovery/Static/Explicit Module Enumeration

Before CAL arrived, I had already made a start on the two applications I referred to earlier. We were using a more traditional layered approach to their construction, utilising WPF commands, .NET events, dependency properties, data binding, data templates etc ... all the normal bits and bobs that WPF and .NET provide in order to get a functional application built. However, things can get very complex very quickly in a large commercial application, and this is where CAL can really help simplify the whole programming model.

At the time, I was working on the project for my employer, along with a couple of contractors, and I cannot for the life of me remember which one of us that 'discovered' CAL and brought it to everyone's attention. We had a cursory look at it, it sparked a few communal light bulbs, and we decided that it needed further investigation. So, we then moved onto reading the Composite Application Guidance documentation and looking through the StockTrader reference implementation and the quick-starts that it comes bundled with. Oh, how we clapped with joy and bounced around the office in an almost euphoric state baring inane grins ...

I guess this paints a favourable picture all in all. CAL isn't for every scenario, so you need to do some homework to work out if it really is going to benefit you in the long run. To really nail it, there is a lot of thinking you should do up front. Some of this thinking is the reason I've put this article together. So, despite what I said above ... here stoppeth the waffeleth! (hopefully)

What I'm going to cover:

  • Basics of creating the shell
  • Dependency Injection / Inversion of Control
  • Event Aggregation
  • MVP
  • Some basics of implementing modules
  • Navigation
  • Compiling your app together in the Dev Environment

In Part 2, I will expand the application into some thing a little more interesting, and I will also cover off how to go about dynamically skinning your application in a CAL fashion.

As you'll notice from the demo code, this is both in multiple solutions and one unified solution to make it easier to get into. The demo application is made up of:

  • Shell
  • ModuleA
  • ModuleB
  • PageManager
  • Navigator
  • StatusBar

VisualStudioSS.jpg

I've done this to help illustrate how a collection of developers can co-ordinate their efforts in order to contribute to the final application. This is something that CAL helps achieve massively; in the CAG, they even talk of having off-shore teams working on modules etc. This needs a lot of co-ordination to cohesively achieve, however. There needs to be a very strong level of agreement in terms of how things are to be structured, and overall guidance is required to make sure that different teams approach the application development with a shared vision. It is very, very easy for different developers/teams to approach things differently and basically compromise the benefits that CAL offers.

Dependency Injection / Inversion of Control

This is like a lot of the things you find yourself doing in life. You do it without thinking, then all of a sudden someone comes along and refers to it as "blah-de-blah", and you go ... "Whoa ... is it???" Well, Dependency Injection is one of those things. It sounds all big and grown up and 'advanced', but it really is little more than having a class without a parameterless constructor – you inject a thing this class depends on to function. OK, that is a stupidly simple explanation, and doesn't even take into account the Unity Container, but it is the crux of the issue really. I've even made a chart!!! This is a very high level overview of just what the Unity Container is doing. In terms of using a basic MVP pattern, when a module is discovered, its Initialize() method is called; this in turn calls an optional RegisterViewsAndServices() method which registers a module's views and services inside the DI container (Unity). Then, when an object is resolved from the container, it will either make an instance of that object and inject the dependencies it finds on the constructor, or it will return a singleton-type instance if the object being resolved has been registered in the container with a ContainerControlledLiftTimeManager.

IUnityContainerInjection.png

Event Aggregation, Commanding, Shared Services - Communication

This is simply awesome in my opinion. You can read more about this in the CAG and on Martin Fowler's website. It is basically a way of providing a loosely coupled (damn, I was trying to write this doc without using that phrase!!, lost that bet!) way to provide communication between entities that are not directly linked; this is also known as indirection. The event aggregator manages a list of events and subscribers, and manages the forwarding of these events to whoever has subscribed to a given event. This is by no means the best and only way to communicate in all situations. It is very easy to misuse this. If you were in a situation where you require a response, for instance, this is not what the event aggregator is designed for; it cannot enforce such a concept, and should not be used in this situation. The event aggregator is essentially a fire and forget method of communication.

The other ways modules can communicate is with shared services within the application. These would most likely be other non-UI modules within the application that a number of other modules use as a service. You would encapsulate the logic and task based stuff inside this 'service provider' module and then expose it through registering its internal types within the UnityContainer with the corresponding interface.

Another method of communication is through commanding. The CAL DelegateCommand<> works in very much the same way a standard WPF command. However, in order for the CanExecute to be evaluated, you have to manually invoke the RaiseCanExecute() method on the command. You can have commands for the Presenters, or you could make use of Global Commands. In the demo presented here, these globally available commands would live in the Core of a module, and anything subscribing to these commands would register themselves here and await execution.

Infrastructure / Shared Cores

The StockTrader reference implementation application has an idea of an Infrastructure library that contains all the shared/common objects that form part of the application. These will range from the DI interfaces, event definitions, event payloads, commands, global commands etc. In contrast to this, my demo does not use this approach. For a large application, this single shared library approach being built / depended on by multiple developers could very easily turn into an unwieldy beast of a library; instead, my demo modules provide a core library should they need to provide access to their innards to other modules. When a module is built, it will be copied into the composite location (where the app is executable), and into a common Int directory; this is where you can reference it statically (the core not the module) in order to "get at" its innards, by placing its interface on a constructor and asking Unity to inject it, or in order to subscribe or publish events.

It is very possible for a module to be so encapsulated that it may only ever need to subscribe or publish event aggregator events, in which case, using this approach, it wouldn't even have a core since no other module needs to directly interact with it, nor it any other module. You can see this in the Navigator module in the demo code.

Shell

The shell in this demo is the main region container. This could actually be farmed off into a module, or it could be contained in the main executable of the application. For this demo and to keep it simple (this is an introduction after all), this is a view in the main executable of the application. Here is the XAML that defines that view and creates the regions and registers them with the RegionManager.

<Window 
    x:Class="JamSoft.CALDemo.Shell"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cal="http://www.codeplex.com/CompositeWPF"
    Title="JamSoft Composite Application Library Demo"
    Background="DarkGray"
    Width="600" 
    Height="600">
     <Grid>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="*" />
             <ColumnDefinition Width="*" />
         </Grid.ColumnDefinitions>
         <Grid.RowDefinitions>
             <RowDefinition Height="35" />
             <RowDefinition Height="*" />
             <RowDefinition Height="25" />
         </Grid.RowDefinitions>
        
         <ItemsControl cal:RegionManager.RegionName="NavigatorRegion"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch"
                      Grid.Column="0"
                      Grid.ColumnSpan="1"
                      Grid.Row="0"
                      Grid.RowSpan="1" />
        
         <ContentControl cal:RegionManager.RegionName="ToolBarRegion" 
                        HorizontalAlignment="Stretch" 
                        VerticalAlignment="Stretch" 
                        Grid.Column="1" 
                        Grid.ColumnSpan="1" 
                        Grid.Row="0" 
                        Grid.RowSpan="1" />

         <Border x:Name="MainRegionBorder" 
                BorderBrush="Black" 
                BorderThickness="2" 
                Grid.Row="1" 
                Grid.RowSpan="1" Grid.ColumnSpan="2" 
                CornerRadius="3" 
                Margin="5" 
                Padding="5">
            
             <ContentControl cal:RegionManager.RegionName="MainRegion"
                            HorizontalAlignment="Stretch" 
                            VerticalAlignment="Stretch" 
                            Grid.Column="0" 
                            Grid.ColumnSpan="2" 
                            Grid.Row="1" 
                            Grid.RowSpan="1" />
         </Border>
        
         <ContentControl cal:RegionManager.RegionName="StatusBarRegion"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch"
                        Grid.Column="0"
                        Grid.ColumnSpan="2"
                        Grid.Row="2"
                        Grid.RowSpan="1"/>
     </Grid>
 </Window>

The executable is also the portion of the application that is the meat of configuring the container through the JamSoftBootstrapper. This class inherits from the UnityBootstrapper. Its job is to create and configure the container and register the shell view in the container. I won't go into the details of this as there is literally a plethora of things you can do here as you would expect, all well beyond the scope of this article. There are all manners of region adaptors that could be included here; there is a rather nifty WindowRegionAdaptor, for example, in the CodePlex CompositeWPFContrib project that you can use for pop-ups and launching other windows with their own regions and views. This actually is an important part of the CAL framework; as with any good framework, it is customisable and extendable. You can even replace the Unity container with the container of your choice, if you like.

The code for this particular Bootstrapper is completely standard, and looks like this:

internal class JamSoftBootstrapper : UnityBootstrapper
{
    protected override IModuleEnumerator GetModuleEnumerator()
    {
        return new DirectoryLookupModuleEnumerator("Modules");
    }

    protected override void ConfigureContainer()
    {
        Container.RegisterType<IShellView, Shell>();

        base.ConfigureContainer();
    }

    protected override DependencyObject CreateShell()
    {
        ShellPresenter presenter = Container.Resolve<shellpresenter>();
        IShellView view = presenter.View;
        view.ShowView();
        return view as DependencyObject;
    }
}

This is kicked off by just a couple of lines in the App.xaml.cs file:

public App()
{
    JamSoftBootstrapper bootStrapper = new JamSoftBootstrapper();
    bootStrapper.Run();
}

It's worth noting here that in the App.xaml file, you'll notice that there is no value associated with the normal StartupUri as this has been farmed off to the ShellPresenter:

<Application 
    x:Class="JamSoft.CALDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
         
    </Application.Resources>
</Application>

The IShellView is the shell's interface that it is registered against in the container; this is really very simple, and looks like this:

public interface IShellView
{
    void ShowView();
}

The ShellPresenter is also very simple, and looks like this:

public class ShellPresenter
{
    public IShellView View { get; private set; }


    public ShellPresenter(IShellView view)
    {
        View = view;
    }
}

Runtime Module Discovery

This is a really neat thing about CAL. The ability for it to discover the modules at runtime. There are a few other methods, and you can also manually add these through various methods in the code or configuration files. I really like the dynamic approach using the DirectoryLookupModuleEnumerator.

Basically, your app starts up, the shell does its bits, and part of that is finding and initialising the modules. With the demo code, I have used the above mentioned IModuleEnumerator. This basically looks at all the DLLs in the specified directory and loads anything that implements IModule and is decorated with ModuleAttribute.

[Module(ModuleName="ModuleA")]

Once that enumeration process is complete, it will work out the module dependencies from ModuleDependencyAttribute:

[ModuleDependency("PageManagerModule")]

Once all of that is complete, it then works through these modules, injects anything on that class' constructor (IUnityContainer at the very least in order to register types), and runs the Initialize() method to prepare the module for use.

MVP

There are loads of variations on this such as MVC, MVVM, etc ... etc ... Suffice to say that it is actually pretty simple. You have a View (V), a Model (M), and a Presenter (P); very often, the M + P are the same thing. The decision on which to use is obviously your call; however, you don't want a massively complex model in your presenter, for instance. That would also be missing the point massively really since these constructs are there to make life easier not harder. Using this as basis for your structure is great taking into account WPF data binding bits; you can end up with a really powerful set of classes with very little actual code. Also, the less code-behind in the view classes, the better.

You can see it at work in these two snippets; the first is implemented on the view that is actually a UserControl:

public interface IStatusBarView
{
    IStatusBarPresentationModel Model { set; }
}

Then, we have the PresenterModel:

public StatusBarPresentationModel(IUnityContainer container, 
                                          IEventAggregator eventAggregator, 
                                          IStatusBarView view)
{
    _container = container;
    _eventAggregator = eventAggregator;

    _view = view;
    _view.Model = this;

    _eventAggregator.GetEvent<AppStatusMessageEvent>().Subscribe(
          OnAppStatusChanged, ThreadOption.UIThread, true);

    AppStatusMessage = "Ready...";
}

What has happened here is that the IStatusBarView has a single property defined as the same type as the interface implemented by our presentation model. So, when our presentation model is resolved from the container, the container makes an instance of the view when it injects the other required bits into the presenter. Then, the presenter sets itself as the model on the view.

The bit that makes all the magic happen is the setter on the Model property in the view's code-behind:

public IStatusBarPresentationModel Model
{
    set 
    {
        this.DataContext = value;
    }
}

Now, all the various bindable bits on our presenter are available within the DataContext of the view, primed for use as WPF Bindings. There really is some great information about this in the CAG stuff, so I won't go into any more detail here.

ModuleA

Yup, I sat up all night thinking about that name ... it's largely irrelevant for this demo anyway. OK, so what we have here is two projects:

  • JamSoft.CALDemo.Modules.ModuleA
  • JamSoft.CALDemo.Modules.ModuleA.Core

In the main module assembly, we have the following bits:

  • ModuleAModule.cs
  • ModuleAPresenterModel.cs
  • ModuleAView.xaml
  • ModuleAView.xaml.cs

In this module's core, we have:

  • IModuleAPresenterModel.cs
  • IModuleAView.cs

These two interfaces could have actually been left inside the main module assembly. We know this since we also know that no other module requests IModuleAPresenterModel anywhere else in the application (you can see a functioning version of this with the Navigator). However, this does provide a nice simple demo of how you can work without an Infrastructure library bringing all these things together.

In turn, module A has static references to its own Core and to the PageManager Core. This is so that the ModuleAPresenter can request a reference to the PageManager by asking the container to inject a reference to IPageManager in order to add itself as an available page in the application. Then, the Navigator module also has a reference to the PageManager Core so it can get a reference to the ObservableCollection<IPage> in order to then make these pages available in the navigator's ListBox for navigating around the application.

Page Manager - Navigation

As you look through the code, you will see that the PageManager module is set to load at start-up so that we can be sure that it is available for presenters to register themselves as an instance of an IPage object and add themselves to the page manager. This isn't absolutely necessary, since registering a module dependency inside modules that need to register a page will ensure that the PageManager module will be initialised before any modules that declare a dependency on it. The Navigator module registers a module dependency on the PageManager as it is the service within the application that it uses to control the view in the application's MainRegion. The Navigator is an ItemsControl region that hosts a ListBox. The ListBox item template is then used to style the items, and look like traditional buttons that users can click in order to navigate to the various pages in the application.

When a page registers itself, we also have a float value that is used to order those pages in the ObservableCollection<IPage> so that we can control the order of the buttons at runtime, baring in mind that the order in which modules are enumerated cannot be relied upon to dictate this ordering in the ListBox. The following code snippet shows the PageManager class:

public class PageManager : IPageManager, INotifyPropertyChanged
{
    private ObservableCollection<IPage> _pages = 
            new ObservableCollection<IPage>();
    private IPage _currentPage;
    private readonly IEventAggregator _eventAggregator;

    public PageManager(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
        _eventAggregator.GetEvent<PageRequestEvent>().Subscribe(
         OnPageSelected, ThreadOption.UIThread);
    }

    private void OnPageSelected(IPage page)
    {
        _currentPage = page;
        _eventAggregator.GetEvent<PageSelectedEvent>().Publish(page);
    }

     public ObservableCollection<IPage> Pages
    {
        get 
        {
            SortPages();
            return _pages;
        }
    }

    public IPage CurrentPage
    {
        get { return _currentPage; }
    }

    private void SortPages()
    {
        List<IPage> p = _pages.ToList<IPage>();
        p.Sort(new PagePositionComparer());
        _pages.Clear();
        foreach (IPage page in p)
        {
            _pages.Add(page);
        }
    }

    public IPage GetPage(string pageId)
    {
        IPage selPage = null;
        for(int i = 0; i < Pages.Count(); i++)
        {
            if (Pages[i].ID == pageId)
            {
                selPage = Pages[i];
            }
        }
        return selPage;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

The following code shows the MainRegionController class:

public class MainRegionController : IMainRegionController
{
    private readonly IUnityContainer _container;
    private readonly IRegionManager _regionManager;
    private readonly IEventAggregator _eventAggregator;

    private object _currentView;
    private IRegion _mainRegion;


    public MainRegionController(IUnityContainer container,
                                IRegionManager regionManager,
                                IEventAggregator eventAggregator)
    {
        _container = container;
        _regionManager = regionManager;
        _eventAggregator = eventAggregator;


        Initialize();
    }


    private void Initialize()
    {
        _eventAggregator.GetEvent<PageSelectedEvent>().Subscribe(
                         PageSelected, ThreadOption.UIThread, true);
        _mainRegion = _regionManager.Regions["MainRegion"];
    }

    private void PageSelected(IPage page)
    {
        if (page != null)
        {
            ShowPage(page);
        }
    }

    private void ShowPage(IPage page)
    {
        object newView = page.View;
        
        if (_currentView!=null)
            _mainRegion.Remove(_currentView);

        if (newView != null)
        {
            _mainRegion.Add(newView);
            _mainRegion.Activate(newView);
        }

        _currentView = newView;
    }
}

To make this particular point complete, we also need to see the NavigatorPresentationModel.cs to see where these PageRequestEvents come from:

public class NavigatorPresentationModel : INavigatorPresentationModel
{
    private readonly INavigatorView _view;
    private readonly IPageManager _pageManger;
    private readonly IEventAggregator _eventAggregator;


    public NavigatorPresentationModel(IEventAggregator eventAggregator,
                                      IPageManager pageManger, 
                                      INavigatorView view)
    {

        _eventAggregator = eventAggregator;
        _pageManger = pageManger;


        _view = view;
        _view.ItemChangeRequest += 
             new EventHandler<PageEventArgs>(view_ItemChangeRequest);
        _view.Model = this;

        _eventAggregator.GetEvent<PageSelectedEvent>().Subscribe(
                         OnPageSelected, ThreadOption.UIThread);
    }

    private void OnPageSelected(IPage page)
    {
        _view.SelectedItem = page;
    }

    void view_ItemChangeRequest(object sender, PageEventArgs e)
    {
        OnItemChangeRequest(e.Page);
    }

    private void OnItemChangeRequest(IPage page)
    {
        _eventAggregator.GetEvent<PageRequestEvent>().Publish(page);
    }

    public INavigatorView View
    {
        get { return _view; }
    }

    public ObservableCollection<IPage> Pages
    {
        get { return _pageManger.Pages ; }
    }
}

So to summarise, the navigator publishes an event defined in the PageManager's Core assembly which the PageManager is in turn subscribed to. This in turn selects the page and then fires off another event to the MainRegionController, which does the actual view switching for the MainRegion defined in the Shell view.

ModuleB

It just contains a presenter and a view and the associated bits to make it a module. It has a static reference to the PageManager Core in order to register itself as a page in the application. However, ModuleB is able to publish a string message to the StatusBar module.

So, ModuleB has three static assembly references to the following cores:

  • JamSoft.CALDemo.Modules.ModuleB.Core
  • JamSoft.CALDemo.Modules.PageManager.Core
  • JamSoft.CALDemo.Modules.StatusBar.Core

In the status bar core, we have a single CompositeWpfEvent defined as follows:

public class AppStatusMessageEvent : CompositeWpfEvent<string>
{
}

The StatusBarPresentationModel subscribes to this event in order to update the status bar message using the EventAggregator:

_eventAggregator.GetEvent<AppStatusMessageEvent>().Subscribe(
         OnAppStatusChanged, ThreadOption.UIThread, true);

This then calls the following code to update the message variable:

private void OnAppStatusChanged(string message)
{
    AppStatusMessage = message;
}

public string AppStatusMessage 
{
    get { return _appStatusMessage; }
    set 
    { 
        _appStatusMessage = value;
        NotifyPropertyChanged("AppStatusMessage");
    }
}

When you enter text in the TextBox supplied in the view and then hit the button, the text from the textbox is "pinged" to a CompositeWpfEvent which has a payload of string. This is then forwarded to the subscribed modules (StatusBar), and each module can then use this to do the required steps. In the case of the StatusBar, it simply prints the text to the screen in the TextBlock within a StatusBarItem. Simple.

Compiling it all Together

How is this all organised? There are a couple of ways of dealing with this. The all in one solution is nice and simple, but if you are working in a team of developers, this is not the best solution.

The demo code is organised into the following directory structure when compiled.

  • bin\
  • bin\Debug
  • bin\Debug\External
  • bin\Debug\Internal
  • bin\Debug\Modules
  • bin\Release
  • bin\Release\External
  • bin\Release\Internal
  • bin\Release\Modules
  • Ext (For third party controls / CAL - basically, source you don't own)
  • Int (Core assemblies you can statically reference, or stuff you do on your own)
  • ModuleA
  • ModuleB
  • Navigator
  • PageManager
  • Shell
  • StatusBar

When you build the app, the various assemblies are distributed using post build events into the various locations within the bin\xxxx directories. External stuff such as third party control DLLs and CAL itself go into the External directory; all the cores and any other DLLs you produce (your own control source code) go into the Internal directory, and the modules themselves go into the Modules directory in order to allow the DirectoyLookupModuleEnumerator to find them at runtime.

The executable is then placed in the root directory (bin\xxxx\) along with a configuration file that contains a definition to the control the directory probes to find the various core assemblies:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="Internal;External"/>
    </assemblyBinding>
  </runtime>
</configuration>

Another way this can be achieved is using environment variables. Your solution structure on disk may be so that this becomes a much more manageable approach, in fact. Once environment variables are set, you are not relying on complete paths to things, nor are you replying on the macros available through the post build event editor within Visual Studio.

That's it for Now

I think I'll leave it there for now. This really is skimming the surface, and to some extent, that was the idea for this article. In the next article, I'm going to expand on this application and build in some more interesting functionality. What I will also cover in Part 2 is a module I have built that powers dynamically discovering style DLLs in much the same way as CAL discovers your modules. Your users can then pick their favourite skin at run-time.

Further Reading and Useful Stuff

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jammer
Chief Technology Officer JamSoft
United Kingdom United Kingdom
Developer and en
Follow on   Twitter

Comments and Discussions

 
QuestionCAL Refrences PinmemberAli Elmi21-May-10 20:28 
AnswerRe: CAL Refrences PinmemberJammer23-May-10 22:30 
AnswerRe: CAL Refrences PinmemberJammer23-May-10 22:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    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 | Mobile
Web03 | 2.8.140721.1 | Last Updated 12 Jun 2009
Article Copyright 2009 by Jammer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid