Click here to Skip to main content
15,861,125 members
Articles / Web Development / HTML

Prism for Silverlight/MEF in Easy Samples. Part 1 - Prism Modules

Rate me:
Please Sign up or sign in to vote.
4.91/5 (246 votes)
24 Sep 2014CPOL16 min read 1.2M   7.3K   300   294
Prism tutorial in easy samples

Download PrismTutorialSamples_PART1.zip - 286.62 KB

Important Note

Friends, I've noticed that the number of downloads for this article far exceeded the number of votes and bookmarks. So please, if you use this tutorial and like it, vote for it. A bookmark won't hurt either:) Thanks.

Introduction

Microsoft Prism is a software framework whose purpose is to simplify building WPF and Silverlight applications.

Prism (together with its source code, samples and documentation) can be downloaded from Prism on the CodePlex.

Prism's main advantage lies in the fact that it allows building the WPF or Silverlight software as a set of almost independent modules that can be developed, tested and debugged separately and brought together by Prism framework to become one application.

The importance of developing software as a number of independent modules cannot be overestimated. The smaller the correlation between the modules the easier it is going to be to modify, test and debug one module without affecting the others or without any interference from the others. When it comes to team development, the module independency becomes even more significant, since multiple developers want to work on their modules with as little worry as possible about changes that others make at the same time.

Prism can be studied based on Prism documentation as well as Prism Quick Starts and other samples that can be downloaded together with Prism source code from Prism on the CodePlex. There is also MS Prism documentation on line and some tutorials that can be found on the Web (e.g. Prism Tutorial by Mark J. Miller).

I hope that this tutorial will be set apart from other tutorials due to the following points:

  • This tutorial is explaining essential Prism functionality based on very simple examples - each example concentrates on no more than a couple of Prism features which are explained in detail. Unfortunately, many Prism samples including the Quick Starts are quite complicated - this makes it more difficult to figure out which features in a Quick Start actually come from Prism and how to use them outside of the Quick Start.
  • Prism primary goal is to make it possible to develop independent modules and bring them together into an application. One of the most important requirements for that is that different modules should not depend on each other via a project reference. Unfortunately many Quick Starts break this requirement by having its main module (the application) referring to other modules. All of the modules in this tutorial are truly independent of each other and from the application.
  • All the samples are MEF based: Prism started as a Unity powered project, but now MEF composition framework is apparently ahead of Unity in terms of adoption by Microsoft (based on the fact that the last Unity source code dump was in May 2010, it seems that Unity is dead, even though some bright people do not think so). Currently Prism works with both MEF and Unity, but many Quick Starts and other tutorials were written some time ago and concentrate on Unity rather than on MEF.
  • All the samples in this tutorial are Silverlight (not WPF) based. There are many other Silverlight based tutorials, but there is a slight edge on the WPF side. Silverlight together with MEF brings another level of complexity, due to the fact that the composition is performed not on the .dll, but on the .xap files. If this tutorial is successful and if I have time I might write another WPF based tutorial in the future.

Overview of the Tutorial

I plan to cover the following parts of Prism in this tutorial (including the future installments).

  1. Prism Modules (covered in this part of the tutorial - part 1).
  2. Navigation (covered in part 2 of this tutorial: Prism for Silverlight/MEF in Easy Samples. Part 2 - Prism Navigation).
  3. Communication between the Modules (covered in part 3 of this tutorial: Prism for Silverlight/MEF in Easy Samples. Part 3 - Communication between the Modules).

I left out Prism commanding on purpose, since from my point of view, using MS Expression SDK's Actions/Behaviors achieves the same goal (of sending communication signals from XAML code to the View Model) at much smaller expense.

The purpose of this tutorial is to provide a comprehensive coverage of essential Prism features by using very simple samples that highlight those features one by one, not discuss the design patterns. Since MVVM code is a little more difficult to understand, I try to avoid it here as much as I can, even though I am a great fan of MVVM myself.

I assume that people reading this tutorial have some knowledge of C#, MEF and Silverlight.

Modularity Overview and Samples

As was stated above, Prism's purpose is to bring independently developed modules into one application. We'll call Prism's main module - Prism application (or simply application) throughout the tutorial and the rest of the modules we'll call simply modules or pluggable modules. Prism application loads the modules as they become available or needed and creates a visual application based on them. Different modules within Prism application can communicate between themselves and with the application using different communication methods described later.

Image 1

Prism application creation and module loading is facilitated by a Bootstrapper class. The bootstrapper can be MEF or Unity based (it will correspondingly derive from MefBootstrapper or UnityBootstrapper classes provided by Prism). Here we will be dealing only with MEF bootstrappers.

Simplest Possible Module Loading Sample

Our first sample is located under SimpleNonVisualModuleSample.sln solution. It is the simplest possible Silverlight project demonstrating loading Prism module into Prism application. It does not even have any visuals (in order to cheat the Silverlight, we set the application's VisualRoot to an empty Grid panel). The only way to prove that the module is loading, is to put a breakpoint within the module's constructor or initializer, run the Silverlight app in the debugger and make sure that the breakpoint is hit.

Let us go over the code involved. The application project: "SimpleNonVisualModuleSample" does not have any view files (since it is a non-visual test application). It only has App.xaml, App.xaml.cs and TheBootstrapper.cs files. App files were created by Visual Studio 2010 when the project was created. App.xaml.cs file had to be slightly modified to run TheBootstrapper within its Application_Startup method instead of assigning the VisualRoot of the application:

private void Application_Startup(object sender, StartupEventArgs e)
{
    // start the bootstrapper
    (new TheBootstrapper()).Run();
}

The most important class within this project is TheBootstrapper. As you can see, it derives from Prism's MefBootstrapper class.

public class TheBootstrapper : MefBootstrapper
{
    protected override void InitializeShell()
    {
        base.InitializeShell();

        // set the visual root of the Silverlight application
        // to the Shell (application's main view)
        Application.Current.RootVisual = (UIElement)Shell;
    }

    // create the shell - shell is the main view for
    // Prism application (in this case we use an empty Grid panel in its place)
    protected override DependencyObject CreateShell()
    {
        return new Grid();
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        ModuleCatalog moduleCatalog = new ModuleCatalog();

        // this is the code responsible
        // for adding Module1 to the application
        moduleCatalog.AddModule
        (
            new ModuleInfo
            {
                InitializationMode = InitializationMode.WhenAvailable,
                Ref = "Module1.xap",
                ModuleName = "Module1Impl",
                ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
            }
        );

        return moduleCatalog;
    }
}

The most interesting method within the bootstrapper is obviously CreateModuleCatalog(). It is responsible for instructing Prism to load Module1 module.

Now, let us take a look at the module code. Module1 project has only one very simple class: Module1Impl:

// ModuleExport attribute makes the module loadable
[ModuleExport(typeof(Module1Impl))]
public class Module1Impl : IModule
{

    #region IModule Members

    public void Initialize()
    {

    }

    #endregion
}

To verify that the module is loaded, put a breakpoint at the Initialize() method of Module1Impl class and start the application in the debug mode. The breakpoint hit means that the module is loaded.

Exercise: try recreating the solution from scratch while changing project and class names by going through the following steps:

  1. Follow Appendix A and Appendix B for detailed instructions on how to create Silverlight Prism Application and Module projects.
    Important Note: Make sure that the directory containing Prism dll files Microsoft.Practices.Prism.dll and Microsoft.Practices.Prism.MefExtensions.dll (which need to be referenced by both Prism Application and Module projects) also contains Microsoft.Practices.ServiceLocation.dll (you can copy them from PrismDll directory of the sample). Microsoft.Practices.ServiceLocation.dll is needed for Prism to work (even though it is not referenced by our projects)
    Another Important Note: Make sure that the references to Prism files within the Module project have their "Copy Local" property set to false as explained in Appendix B. Otherwise MEF is not going to work.
  2. Once the projects are created, remove MainView.xaml and MainView.xaml.cs files from both of them and remove App.xaml and App.xaml.cs file from the Module project.
  3. Add the bootstrapper class to Application project; add the required code to it and replace the body of Application_Startup method within App.xaml.cs file of the application project to run the bootstrapper.
  4. Add the module implementation class to the Module project and add the required code to it.
  5. Test that the module is loaded by putting a breakpoint in the Module's Initialize method and running an application within a debugger.
  6. If the breakpoint is hit, give yourself a pat, if it is not, try to figure out what is wrong by checking each step carefully and by comparing your code with the sample code.

Visual Prism Application with Module Loading Sample

While the previous sample virtually did not have any visuals, in this example, both the Application and the Module will have them. The application view (called the Shell) figures out where to stick the module view in, based on the region names.

The source code for this sample is located under SimpleVisualModuleLoadingApp.sln solution. Once you run it you'll see the following screen:

Image 2

The Shell view contains "This is the Shell, Not a Module" text while the module contains "This is Module1" text.

Let us look at the code. The projects were created in exactly the same fashion as those of the previous sample, only we did not remove MainView.xaml and MainView.xaml.cs files. Instead they and their classes were renamed to Shell and Module1View for the application and module views correspondingly.

In this particular example it does not matter but with an eye to future examples, we turn Shell into MEF composable class by adding [Export] attribute to it. It will allow MEF composing it, in case it has some MEF imports. Shell's MEF composition is achieved by overriding ConfigureAggregateCatalog method of MefBootstrapper class within TheBootstrapper class which we do by adding the following line of code:

// Prism's AggregateCatalog is a catalog of all MEF composable parts
// within the application.
// We add the parts corresponding to the current assembly to it
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));

This line adds the current application assembly's MEF parts to all MEF composable parts, controlled by Prism's AggregationCatalog.

Shell.xaml file contains "This is the Shell, Not a Module" TextBlock. It also contains the placeholder for a module view:

XML
<ContentControl HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   prism:RegionManager.RegionName="MyRegion1"/>

RegionName is an attached variable defined within Prism's RegionManager class that allows to specify the region for plugging in module view(s).

Switching to the module source code, we observe that Module1Impl class now MEF imports IRegionManager from Prism:

// import IRegionManager singleton from Prism
[Import]
public IRegionManager TheRegionManager { private get; set; }

We also notice that Initialize() function now registers the Module1View with "Region1" region name:

public void Initialize()
{
    TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View));
}

The module's view is placed over the Application's control based on the region name match.

Notice that TheRegionManager.RegisterViewWithRegion forces a view construction (via MEF or based on a delegate). This method of identifying a view with a region is called View Discovery. Another method allows inserting an already created view into a region and it is called View Injection. View Injection will be described further.

Module1View is made to be a MEF composable class by adding Export attribute to it:

[Export(typeof(Module1View))]
public partial class Module1View : UserControl
{
    public Module1View()
    {
        InitializeComponent();
    }
}

Finally Module1View.xaml file has a very simple content: a "This is Module1" TextBlock.

Exercise: Create a similar sample on your own, changing the project, class and region names. Make sure it runs and displays the module view.
Important Note: Make sure that the references to Prism files within the Module project have their "Copy Local" property set to false as explained in Appendix B. Otherwise MEF is not going to work. (And this is really the last time I mention it).

Optional Exercise
One can see that we used ContentControl above to host a model view within the main application (remember Shell.xaml file?) Prism also allows using other controls for this purpose: Selector, ItemsControl and TabControl. Those controls allow plugging in multiple views within the same region. These views will be arranged based on the corresponding control's functionality.

I leave it as an optional exercise to create another view within Module1, register this newly created view with "MyRegion1" within the module, while changing the ContentControl within Shell.xaml file to the following:

XML
<ItemsControl HorizontalAlignment="Center"
              VerticalAlignment="Center"
              prism:RegionManager.RegionName="MyRegion1">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

As a result the different module views will be arranged according to the StackPanel's layout: one on top of the other and the application will look like this:

Image 3

Unfortunately since the downloading is local and the downloaded module is small, you won't be able to see a gradual change of the progress par - everything will be downloaded at once.

Exercise: build a similar application yourself.

Downloading a Module on Demand Sample

All the samples above have their modules downloaded "when available", i.e. when the module becomes available for download at the server side, which in our samples meant during the application initialization. This was achieved by setting InitializationMode of the corresponding module to InitializationMode.WhenAvailable value within CreateModuleCatalog() method of the bootstrapper:

moduleCatalog.AddModule
(
    new ModuleInfo
    {
        // THIS IS WHERE WE SET THE MODULE'S INITIALIZATION MODE!
        InitializationMode = InitializationMode.WhenAvailable,
        Ref = "Module1.xap",
        ModuleName = "Module1Impl",
        ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
    }
);

If we change the initialization mode to InitializationMode.OnDemand the module will not be loaded at the initialization stage. In fact one would have to write code that uses ModuleManager class (introduced in the previous sample) to load the module.

The code for the sample is located under ModuleLoadingOnDemandSample.sln solution. With respect to the previous sample only Shell.xaml and Shell.xaml.cs files are changed.

Shell.xaml now has a button to trigger module download.

Shell.xaml.cs file stores the module manager object obtained within its constructor in _moduleManager class field. Once the "Download Module" button is clicked, LoadModule method of the ModuleManager is called to download the module:

void DownloadModuleButton_Click(object sender, RoutedEventArgs e)
{
    // we pass the module name to LoadModule method
    _moduleManager.LoadModule("Module1Impl");
}

When the module is loaded the button is disabled:

_moduleManager.LoadModuleCompleted += _moduleManager_LoadModuleCompleted;
...
void _moduleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
    DownloadModuleButton.IsEnabled = false;
}

To verify that the sample is working, make sure there is no module view displayed within the Shell at the start. Then click the "Download Module" button, the module view should appear in the middle of the window and the button should get disabled.

Exercise: create a similar demo.

Module Dependency Sample

Prism allows some "On Demand" modules to be loaded automatically if another module is loaded that depends on them. Module dependency can be specified the when module catalog is getting populated within the bootstrapper's CreateModuleCatalog method.

The source code for this sample is located under ModuleDependencySample.sln solution.

There are two modules to be loaded in the application (instead of the usual one): Module0 and Module1. Module1 depends on Module0, as defined in the bootstraper's CreateModuleCatalog function:

protected override IModuleCatalog CreateModuleCatalog()
{
    ModuleCatalog moduleCatalog = new ModuleCatalog();

    // this is the code responsible
    // for adding Module0 to the application
    moduleCatalog.AddModule
    (
        new ModuleInfo
        {
            InitializationMode = InitializationMode.OnDemand,
            Ref = "Module0.xap",
            ModuleName = "Module0",
            ModuleType = "Module0.Module0Impl, Module0, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
        }
    );

    // this is the code responsible
    // for adding Module1 to the application
    moduleCatalog.AddModule
    (
        new ModuleInfo
        {
            InitializationMode = InitializationMode.OnDemand,

            // set depends on property to include "Module0"
            DependsOn = new System.Collections.ObjectModel.Collection<string>{"Module0Impl"},
            Ref = "Module1.xap",
            ModuleName = "Module1",
            ModuleType = "Module1.Module1Impl, Module1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
        }
    );

    return moduleCatalog;
}

This dependency means that Module0 always loads before Module1. To verify this, Shell.xaml region control was changed to an ItemsControl whose ItemsPanel arranges the loaded views in order they are loaded:

XML
<ItemsControl HorizontalAlignment="Center"
              VerticalAlignment="Center"
              prism:RegionManager.RegionName="MyRegion1">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <StackPanel />
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
</ItemsControl>

The result of pushing the "Download Module" button is shown below:

Image 4

As you can see, both modules are loaded and Module0 is loaded before Module1.

Exercise: Create a similar demo.

View Injection

Registering the module views with the regions as we did above is called View Discovery. Here is typical code for View Discovery (usually located within a module's Initialize() method:

TheRegionManager.RegisterViewWithRegion("MyRegion1", typeof(Module1View));

The above method, however, always results in a new view being created - if we want to create the view ourselves and then put it into a region, we need to resort to View Injection instead.

The View Injection sample is located under ViewInjectionSample.sln solution.

By looking at Shell.xaml file, you can see that there are two regions: "MyRegion1" and "MyRegion2". "MyRegion1" region simply displays the module view (which is discovered) as before. "MyRegion2", however is used for injecting very simple views (consisting of just one TextBlock).

If we take a look at the module's Module1View.xaml file, we'll see that the module view has an "Inject View" button. Pressing this button will trigger the view injection code in Module1View.xaml.cs file:

void InjectViewButton_Click(object sender, RoutedEventArgs e)
{
    IRegion region = TheRegionManager.Regions["MyRegion2"];

    // View Injection
    region.Add
     (
        new TextBlock
        {
            Text = "Hello" + ++i,
            FontSize = 20,
            Foreground = new SolidColorBrush(Colors.Blue)
        }
    );
}

Here is how the application is going to look after a few "Inject View" button clicks:

Image 5

Exercise: Create a similar demo.

Conclusion

This is a detailed overview of Prism Module functionality in small and simple samples. I'd appreciate very much if you could vote for this article and leave a comment. I would love to hear your suggestions for improving and expanding the content of this tutorial.

More tutorials are coming focusing on Prism Navigation and Communication functionality.

History

Feb 14, 2011 - changed the source code to use the newest Prism dlls (from Prism version 52595) - the code changes were very minor - only one project (with module dependency) was affected.

Jan. 08, 2012 - removed dead links to Eric Mork's tutorials. Hat tip to reader onefootswil for listing them.

Jan. 18, 2012 - added correct html anchors for the appendices. Hat tip to reader Sunara Imdadhusen for pointing out that the anchors were missing and the hyperlinks to the appendices were not working.

Appendix A: Creating the Prism Application Silverlight Project in Visual Studio 2010

Important Note: In order to be able to work with Silverlight in VS 2010, you need to install Microsoft Silverlight 4 Tool for Vistual Studion 2010.

Following are the steps for creating prism's main module (application) project:

  1. Open VS 2010. Go to File menu -> New -> Project
  2. In the opened window choose "Silverlight Application" as the project type, make sure .Net framework 4 is selected, select the name and directory for your project, as shown on the picture below:

    Image 6

    Click OK button.
  3. Leave all defaults, unless you want to change the name of your Silverlight hosting web project:

    Image 7

    Click OK button.
  4. Add references to Microsoft.Practices.Prism.dll and Microsoft.Practices.Prism.MefExtensions.dll files by right mouse click on References under the Silverlight project and choosing "Add Reference", choosing Browse tab, browsing to the directory containing Prism .dll files and selecting those files:

    Image 8

    Then click OK button.
    Important Note: Make sure that the directory containing these Prism dlls also contains Microsoft.Practices.ServiceLocation.dll (you can copy them from PrismDll directory of the sample). Microsoft.Practices.ServiceLocation.dll is needed for Prism to work (even though it is not referenced by our projects).
  5. Add reference to MEF composition dll (which is part of .NET 4) by going through the similar steps as above, only choosing .NET tab and selecting System.ComponentModel.Composition reference:

    Image 9

Appendix B: Creating a (non-Main) Module Silverlight Project in Visual Studio 2010

Creating a module project is very similar to creating the main module (application) project.

Important Note: The deployment unit in Silverlight is an .xap file, not a .dll library, because of that the modules should be also created as Silverlight Applications, not Silverlight Class Libraries.

Here are the steps for creating a project for a Prism module:

  1. Right click on the solution within VS 2010 Solution Explorer and choose Add->"New Project" to initiate the module project creation.
  2. Just as in case of the application project, choose "Silverlight Application" as the project type, also choose the project name and location

    Image 10

    Important Note: Not setting Copy Local to False on Prism references leads to MEF errors. It took me only a day to figure out why my modules do not load within my Prism application.
    Another Important Note: just like in case of Prism application, make sure that the directory containing these Prism dlls also contains Microsoft.Practices.ServiceLocation.dll (you can copy them from PrismDll directory of the sample). Microsoft.Practices.ServiceLocation.dll is needed for Prism to work (even though it is not referenced by our projects).
  3. Just like you did in case of Prism application, add System.ComponentModel.Composition .NET reference.
  4. Remove the App.xaml and App.xaml.cs files from the Module project by right clicking on them and choosing "Delete" within the Visual Studio Solution Explorer.

License

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


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
GeneralMy vote of 5 Pin
yang tim17-Mar-13 22:39
yang tim17-Mar-13 22:39 
GeneralRe: My vote of 5 Pin
Nick Polyak18-Mar-13 14:28
mvaNick Polyak18-Mar-13 14:28 
GeneralMy vote of 5 Pin
Bayo Adegbola7-Mar-13 3:35
professionalBayo Adegbola7-Mar-13 3:35 
GeneralRe: My vote of 5 Pin
Nick Polyak9-Mar-13 13:39
mvaNick Polyak9-Mar-13 13:39 
GeneralMy vote of 5 Pin
Savalia Manoj M5-Mar-13 18:34
Savalia Manoj M5-Mar-13 18:34 
GeneralRe: My vote of 5 Pin
Nick Polyak6-Mar-13 17:22
mvaNick Polyak6-Mar-13 17:22 
GeneralGood Pin
iamflwcode27-Feb-13 22:09
iamflwcode27-Feb-13 22:09 
GeneralRe: Good Pin
Nick Polyak28-Feb-13 13:36
mvaNick Polyak28-Feb-13 13:36 
Thanks
Nick Polyak

GeneralMy vote of 5 Pin
HariPrasad katakam29-Oct-12 1:14
HariPrasad katakam29-Oct-12 1:14 
GeneralRe: My vote of 5 Pin
Nick Polyak29-Oct-12 1:50
mvaNick Polyak29-Oct-12 1:50 
GeneralMy vote of 5 Pin
victor_rg27-Sep-12 7:08
victor_rg27-Sep-12 7:08 
GeneralRe: My vote of 5 Pin
Nick Polyak27-Sep-12 7:53
mvaNick Polyak27-Sep-12 7:53 
GeneralVery Good Article Pin
nwaf9-Sep-12 1:04
professionalnwaf9-Sep-12 1:04 
GeneralRe: Very Good Article Pin
Nick Polyak9-Sep-12 7:44
mvaNick Polyak9-Sep-12 7:44 
GeneralMy vote of 5 Pin
dexter7416-Aug-12 5:59
dexter7416-Aug-12 5:59 
GeneralRe: My vote of 5 Pin
Nick Polyak17-Aug-12 12:49
mvaNick Polyak17-Aug-12 12:49 
QuestionGetting error after running part 1 and 2 Pin
kirangh12-Aug-12 23:38
kirangh12-Aug-12 23:38 
AnswerRe: Getting error after running part 1 and 2 Pin
Nick Polyak13-Aug-12 3:45
mvaNick Polyak13-Aug-12 3:45 
GeneralRe: Getting error after running part 1 and 2 Pin
kirangh13-Aug-12 18:24
kirangh13-Aug-12 18:24 
GeneralRe: Getting error after running part 1 and 2 Pin
Nick Polyak14-Aug-12 9:41
mvaNick Polyak14-Aug-12 9:41 
GeneralRe: Getting error after running part 1 and 2 Pin
kirangh15-Aug-12 23:11
kirangh15-Aug-12 23:11 
GeneralRe: Getting error after running part 1 and 2 Pin
Nick Polyak17-Aug-12 12:49
mvaNick Polyak17-Aug-12 12:49 
QuestionExcellent Pin
shish20108-Aug-12 18:21
shish20108-Aug-12 18:21 
AnswerRe: Excellent Pin
Nick Polyak13-Aug-12 3:43
mvaNick Polyak13-Aug-12 3:43 
GeneralMy vote of 5 Pin
free_will6-Aug-12 13:31
free_will6-Aug-12 13:31 

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.