Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

A Practical Quick-start Tutorial on MVVM in WPF

Rate me:
Please Sign up or sign in to vote.
4.76/5 (72 votes)
21 May 2010CPOL11 min read 479.2K   18.4K   257   45
This article gives a practical quick-start tutorial on MVVM in WPF for application developers.

Introduction

This article gives a practical quick-start tutorial on MVVM pattern in WPF for application developers.

Background

The MVVM (Model-View-ViewModel) Design Pattern is a Design Pattern introduced recently in the software development community. This Design Pattern is a specialized Design Pattern for WPF and Silverlight applications. The following picture borrowed from Xianzhong Zhu's article "A WaspKiller Game with Silverlight 3, .NET RIA Services, MVP, and MVVM Patterns" shows the MVVM pattern graphically:

MVVM.jpg

According to Wikipedia, the MVVM pattern can be described as follows:

  • Model: As in the classic MVC pattern, the Model refers to either:
    1. an object model that represents the real state content (an object-oriented approach), or
    2. the data access layer that represents the content (a data-centric approach).
  • View: As in the classic MVC pattern, the View refers to all elements displayed by the GUI such as buttons, windows, graphics, and other controls.
  • ViewModel: The ViewModel is a "Model of the View" meaning it is an abstraction of the View that also serves in data binding between the View and the Model. It could be seen as a specialized aspect of what would be a Controller (in the MVC pattern) that acts as a data binder/converter that changes Model information into View information and passes commands from the View into the Model. The ViewModel exposes public properties, commands, and abstractions. The ViewModel has been likened to a conceptual state of the data as opposed to the real state of the data in the Model.

You can find many tutorials on developing MVVM WPF and Silverlight applications on the web. This article is intended to give the application developer a practical quick-start by quickly creating an MVVM WPF application that strictly follows Microsoft's recommendations.

This article assumes that readers can create some basic WPF applications, create "XAML" files, and understand the basics of "bindings". The tutorial application is developed in Visual Studio 2008, and the language used is C#.

Let us start this tutorial by first setting up the development environment.

Set up the development environment

In order to quickly create our own WPF MVVM applications, let us first download the "WPF Model-View-ViewModel Toolkit". You can go to the "CodePlex" website to download the toolkit. The version of the toolkit used in this article is "0.1".

Create a WPF MVVM application

After the toolkit is installed, we can then create a WPF MVVM application. Launch Visual Studio to create a new project. The Visual Studio will let you select a project type:

CreateNewProject.jpg

When installing the "WPF Model-View-ViewModel Toolkit", a project template "WPF Model-View Application" is added to Visual Studio. Select this template and give the application the name "WpfModelViewDemoApplication", browse a folder where you want to save the Visual Studio generated files, and click the "OK" button; the WPF MVVM project is generated. The following picture shows the files generated by the template:

SolutionExplorerEmpty.jpg

Like most WPF applications, this application's starting point is the code-behind file of the "App.xaml" file. The folders "Models", "Views", and "ViewModels" are used for developing the Models, Views, and the View-Models. We need to pay some special attention to these three files:

  • Commands\CommandReferences.cs
  • Commands\DelegatedCommand.cs
  • ViewModels\ViewModelBase.cs

These files are created by the template to help application developers to manage the routed commands and events. The "ViewModelBase" class implemented in the "ViewModels\ViewModelBase.cs" file should be the base class for all the Views in MVVM. In developing our own MVVM applications, we normally do not need to make any changes to these files. We can simply use them to develop MVVM applications that follow the Microsoft recommendations. We should spend most of our development effort to better focus on the IT and business aspects of the software projects.

Although it does not have a Model, the application created by the template is a runnable MVVM application. Let us now compile and run the application. Press the "F5" key when Visual Studio is in focus, the application will start and launch the "Views\MainView.xaml" WPF window, which is called a View in the MVVM pattern.

RunAppEmpty.jpg

If we select "File" from the menu, and click the "Exit" menu item, the application will stop. Now let us take a look at the code-behind file of the "MainView.xaml" WPF window:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfModelViewDemoApplication.Views
{
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
        }
    }
}

You will notice that this code-behind file does not have any event handlers. How can the application shut down when we select "Exit" from the drop-down menu? The answer is in the three files created by the template. It seems that Microsoft favors removing all the event handlers from the code-behind files for the Views and moving them to the View-Models. The Event handler for the "Exit" command is implemented in the "MainView.xaml" file's View-Model in the "ViewModels\MainViewModel.cs" file:

C#
private DelegateCommand exitCommand;
public ICommand ExitCommand
{
        get
        {
         if (exitCommand == null)
                {
                    exitCommand = new DelegateCommand(Exit);
                }
                return exitCommand;
        }
}
 
private void Exit()
{
    Application.Current.Shutdown();
}

With the help of the three files, the "Exit" command will be routed to "ViewModels\MainViewModel.cs" and the "Exit" method implemented here will be executed.

This article is intended to give you a quick-start on developing WPF MVVM applications. I will leave these three files as black boxes, and only use the functions provided. If you are interested in how the "Exit" command is routed to the "Exit" method, you can take a look at these files and take a look at some articles such as this one.

In theory, we can finish this tutorial now, because we have implemented a fully runnable WPF MVVM application. But this application does not have any function yet. In the following parts of the tutorial, I will add a model class, and implement some functions based on this template WPF MVVM application. I will create an application that shows a list of students and allows you to add additional students.

This article comes with the source code for the tutorial application; it is recommended that you download it and run the application before you proceed to read the rest of the article. This will give you a better idea on what we will be doing. If you encounter some run-time error when you run the application in Debug mode, you can refer to the later part of the article to adjust your Visual Studio settings.

Add a model class

To add some functions to the WPF MVVM application, we will first create the application's data model. The model classes are created in the "Models" folder in the C# file "StudentsModel.cs":

C#
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WpfModelViewDemoApplication.Models
{
    public class Student
    {
        public string Name {get; set;}
        public int Score {get; set;}
        public DateTime TimeAdded {get; set;}
        public string Comment {get; set;}
 
        public Student(string Name, int Score,
            DateTime TimeAdded, string Comment) {
            this.Name = Name;
            this.Score = Score;
            this.TimeAdded = TimeAdded;
            this.Comment = Comment;
        }
    }
 
    public class StudentsModel: ObservableCollection<student />
    {
        private static object _threadLock = new Object();
        private static StudentsModel current = null;
 
        public static StudentsModel Current {
            get {
                lock (_threadLock)
                if (current == null)
                    current = new StudentsModel();
 
                return current;
            }
        }
 
        private StudentsModel() {
 
            Random rd = new Random();
            for (int Idex = 1; Idex <= 5; Idex++)
            {
                string Name = "Student Name No. " + Idex.ToString();
                int Score = 
                    System.Convert.ToInt16(60 + rd.NextDouble() * 40);
                DateTime TimeAdded = System.DateTime.Now;
                string Comment = "This student is added @ " +
                    TimeAdded.ToString();
 
                Student aStudent = new Student(Name, Score,
                    TimeAdded, Comment);
                Add(aStudent);
            }
        }
 
        public void AddAStudent(String Name,
            int Score, DateTime TimeAdded, string Comment) {
            Student aNewStudent = new Student(Name, Score,
                TimeAdded, Comment);
            Add(aNewStudent);
        }
    }
}

The "StudentsModel.cs" file implements two classes:

  • Student
  • StudentsModel

The class "Student" represents a single student, which has the information about the name, the score, the time added to the system, as well as some comments on the student. The "StudentsModel" class is an "ObservableCollection" of "Student" objects. This class is implemented as a thread safe singleton class. When constructing this class, the constructor inserts five randomly generated students into the "ObservableCollection". These students will be listed when the application starts up.

Modify the View-Model in the "ViewModels\MainViewModel.cs" file

In a WPF MVVM application, the Models will go through the View-Model classes to communicate with the Views (UI). In this tutorial, I will not create new View-Model classes. I will modify the View-Model implemented in "ViewModels\MainViewModel.cs":

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using WpfModelViewDemoApplication.Models;
using WpfModelViewDemoApplication.Commands;

namespace WpfModelViewDemoApplication.ViewModels
{
    public class MainViewModel : ViewModelBase
    {
        private DelegateCommand exitCommand;
 
        #region Constructor
 
        public StudentsModel Students { get; set; }
        public string StudentNameToAdd { get; set; }
        public int StudentScoreToAdd { get; set; }
 
 
        public MainViewModel()
        {
            Students = StudentsModel.Current;
        }
 
        #endregion
 
        public ICommand ExitCommand
        {
            get
            {
                if (exitCommand == null)
                {
                    exitCommand = new DelegateCommand(Exit);
                }
                return exitCommand;
            }
        }
 
        private void Exit()
        {
            Application.Current.Shutdown();
        }
 
        private ICommand _AddStudent;
        public ICommand AddStudent
        {
            get
            {
                if (_AddStudent == null)
                {
                    _AddStudent = new DelegateCommand(delegate()
                    {
                        StudentNameToAdd.Trim();
 
                        StringBuilder SB = new StringBuilder();
                        if (StudentNameToAdd == "")
                        {
                            SB.Remove(0, SB.Length);
                            SB.Append("Please type in a name for the student.");
                            throw new ArgumentException(SB.ToString());
                        }
 
                        if (StudentNameToAdd.Length < 10)
                        {
                            SB.Remove(0, SB.Length);
                            SB.Append("We only take students whose name is longer than ");
                            SB.Append("10 characters.");
                            throw new ArgumentException(SB.ToString());
                        }
                        if ((StudentScoreToAdd < 60) || (StudentScoreToAdd > 100))
                        {
                            SB.Remove(0, SB.Length);
                            SB.Append("We only take students " + 
                                      "whose score is between 60 and 100. ");
                            SB.Append("Please give a valid score");
                            throw new ArgumentException(SB.ToString());
                        }
 
                        DateTime Now = DateTime.Now;
                        SB.Remove(0, SB.Length);
                        SB.Append("Student ");
                        SB.Append(StudentNameToAdd);
                        SB.Append(" is added @ ");
                        SB.Append(Now.ToString());
 
                        Students.AddAStudent(StudentNameToAdd,
                            StudentScoreToAdd, Now, SB.ToString());
                    });
                }
 
                return _AddStudent;
            }
        }
    }
}

This View-Model class inherits from the "ViewModelBase" class implemented in the "ViewModels\ViewModelBase.cs" file. If you want to create your own View-Models, you should also make your View-Model classes sub-classes of the "ViewModelBase" class, so you can better use the functions generated by the "WPF Model-View Application" template. The "ViewModelBase" class implements the "INotifyPropertyChanged" interface, so the UI (View) will be notified when a public property bounded to it is changed.

Without changing any existing functions in the "MainViewModel" class, we add four public properties:

  • public StudentsModel Students
  • public string StudentNameToAdd
  • public int StudentScoreToAdd
  • public ICommand AddStudent

Three of the properties are related to "Data Binding". The property "Students" holds a reference to the application's singleton model class "StudentsModel". The properties "StudentNameToAdd" and "StudentScoreToAdd" are used to take inputs from the user, so new students can be added to the Model.

The property "AddStudent" returns an "ICommand" interface reference which is used for "Command Binding". To add a student, the "DelegateCommand" implemented in this property will first validate the public properties "StudentNameToAdd" and "StudentScoreToAdd" to check if the user gives a valid student name and a valid score. If the input information is not valid, an exception is thrown. The "Exception" objects contain a detailed message about the problems encountered when adding a "Student".

Add a "DispatcherUnhandledException" handler to "App.xaml"

The View-Model class that we just implemented throws "Exceptions". We need to handle these exceptions, otherwise the application will stop. We will implement the exception handler in the code-behind file for the "App.xaml" file as the method "APP_DispatcherUnhandledException":

C#
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Windows;
using System.Linq;
using System.Windows.Threading;

namespace WpfModelViewDemoApplication
{
    public partial class App : Application
    {
        private void OnStartup(object sender, StartupEventArgs e)
        {
            Views.MainView view = new Views.MainView();
            view.DataContext = new ViewModels.MainViewModel();
            view.Show();
        }
 
        private void APP_DispatcherUnhandledException(object sender, 
            DispatcherUnhandledExceptionEventArgs e)
        {
            MessageBox.Show(e.Exception.Message);
            e.Handled = true;
        }
    }
}

In order that the exception handler catches the exceptions, we will need to modify the "App.xaml" file to set the "DispatcherUnhandledException" property in the "<Application />" tag to "APP_DispatcherUnhandledException":

XML
<Application x:Class="WpfModelViewDemoApplication.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    DispatcherUnhandledException="APP_DispatcherUnhandledException"
    Startup="OnStartup">
    <Application.Resources>
         
    </Application.Resources>
</Application>

This exception handler only catches unhandled exceptions thrown from the UI thread. If the application has worker threads that also throw exceptions, these exceptions need to be "Dispatched" to the UI thread to be caught by this handler.

Modify "MainView.xaml"

We will keep using "MainView.xaml" as the main application View. We will keep all the functions created by the template, and add the components needed by the application:

XML
<Window x:Class="WpfModelViewDemoApplication.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:c="clr-namespace:WpfModelViewDemoApplication.Commands"
    FontFamily="Verdana"
    Title="WPF MVVM Tutorial">
    
<Window.Resources>
    <c:CommandReference x:Key="ExitCommandReference"
                        Command="{Binding ExitCommand}" />
        <Style x:Key="LabelStyle" TargetType="{x:Type TextBlock}">
            <Setter Property="FontWeight" Value="Bold" />
            
        </Style>
        <Style x:Key="GridViewHeaderStyle"
               TargetType="{x:Type GridViewColumnHeader}">
            <Setter Property="FontWeight" Value="Bold" />
            <Setter Property="Foreground" Value="Maroon" />
            <Setter Property="Background" Value="LightSkyBlue" />
        </Style>
    </Window.Resources>
   
<Window.InputBindings>
    <KeyBinding Key="X" Modifiers="Control"
                Command="{StaticResource ExitCommandReference}" /> 
</Window.InputBindings>
 
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="40" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
 
    <Menu Grid.Row="0">
        <MenuItem Header="_File">
            <MenuItem Command="{Binding ExitCommand}"
                      Header="E_xit" InputGestureText="Ctrl-X" />
        </MenuItem>
    </Menu>
 
    <Grid Grid.Row="1" HorizontalAlignment="Right"
          Margin="0, 5, 20, 10" VerticalAlignment="Center">
        <StackPanel Orientation="Horizontal">
            <TextBlock Style="{StaticResource  LabelStyle}">
                Student name</TextBlock>
            <TextBox Width="200" Margin="10, 0, 5, 0"
                     Text="{Binding Path=StudentNameToAdd,
                Mode=OneWayToSource}">
            </TextBox>
            <TextBlock Style="{StaticResource  LabelStyle}">
                Score</TextBlock>
                <TextBox Width="100" Margin="10, 0, 5, 0"
                         Text="{Binding Path=StudentScoreToAdd,
                    Mode=OneWayToSource}">
                </TextBox>
            
                    <Button x:Name="btnAddStudent"
                            Content="Add a student"
                            Command="{Binding AddStudent}">
            </Button>
        </StackPanel>
    </Grid>
 
        <ListView  Grid.Row="2" BorderBrush="White"
                   ItemsSource="{Binding Path=Students}"
                   HorizontalAlignment="Stretch">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name"
                                    HeaderContainerStyle=
                                    "{StaticResource GridViewHeaderStyle}"
                                    DisplayMemberBinding="{Binding Path=Name}" />
                    <GridViewColumn Header="Score"
                                    HeaderContainerStyle=
                                    "{StaticResource GridViewHeaderStyle}"
                                    DisplayMemberBinding="{Binding Path=Score}" />
                    <GridViewColumn Header="TimeAdded"
                                    HeaderContainerStyle=
                                    "{StaticResource GridViewHeaderStyle}"
                                    DisplayMemberBinding="{Binding Path=TimeAdded}" />
                    <GridViewColumn Header="Comment"
                                    HeaderContainerStyle=
                                    "{StaticResource GridViewHeaderStyle}"
                                    DisplayMemberBinding="{Binding Path=Comment}" />
                </GridView>
            </ListView.View>
        </ListView >
    </Grid>
</Window>

Besides adding the styles, I added a "ListView" to display the list of students in our "StudentsModel". I also added two "TextBox" controls to take the user's input for the student name and score. The "Button" in this "XAML" file issues the command to add a new student.

The "ItemsSource" of the "ListView" is bound to the "Students" property of the View-Model class "MainViewModel", and the two "TextBox" controls are each bound to the "StudentNameToAdd" and "StudentScoreToAdd" properties. The "Command" property of the "Add a student" button is bound to the "ICommand" property "AddStudent" in the View-Model, so the "DelegateCommand" implemented in the View-Model class will be called to add a new student.

The code-behind file of the "MainView.xaml" file is no longer responsible for handling the user commands, it will only set the "DataContext" of "MainView.xaml" to an instance of the View-Model class.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using WpfModelViewDemoApplication.ViewModels;

namespace WpfModelViewDemoApplication.Views
{
    public partial class MainView : Window
    {
        public MainView()
        {
            InitializeComponent();
            DataContext = new MainViewModel();
        }
    }
}

Run the application

Now we have finished adding some functions to the MVVM application. In order to run the application smoothly in Debug mode, we will need to make some adjustments to the Visual Studio settings. This is because the application throws exceptions, and we do not want the application to stop at the exceptions. We want the exception to be caught by the application level exception handler "APP_DispatcherUnhandledException". Go to the "Debug" menu in Visual Studio and select "Exceptions":

DebugExeptionMenu.jpg

Un-check the "user-unhandled" checkbox for "Common Language Runtime Exceptions" and click the "OK" button.

DebugExeptionConfig.jpg

After adjusting the settings of Visual Studio, we can debug run the application. The following picture shows the list of the students. Give a name and a score to a new student and click the "Add a student" button, you will see that the student is added. You can also give some very low or very high scores to the student to see if the validations of the student information work.

RunApp.jpg

Points of interest

  • This article gave a practical quick-start tutorial on MVVM for application developers. With the help of the "WPF Model-View-ViewModel Toolkit", creating a WPF MVVM application is very simple and easy.
  • This article did not go to the details in the files created by the "WPF Model-View Application" template, but simply used them. If you are interested, you can take a look at these files to get a better understanding of the "Routed Commands".
  • For simplicity, the validation of the user input data in the View-Model class uses "Exceptions" to report the validation errors. You may be able to find better ways to do data validations, the choice of this method is only for the simplicity in this article.
  • This tutorial is intended to give you a quick-start. If you want to learn more about MVVM, there are a lot of other references, and I strongly recommend you to take a look at them.

Conclusion

To conclude this article, I would like to borrow some information from Wikipedia, which comes from the creator of MVVM John Gossman:

The overhead in implementing MVVM is "overkill" for simple UI operations. For larger applications, generalizing the View layer becomes more difficult. Moreover, data binding, if not managed well, can result in considerable memory consumption in an application.

What John said should be true. Even with the help of the "WPF Model-View-ViewModel Toolkit", you may notice that the implementation of this tutorial application is fairly complex, which can be otherwise implemented much simpler in many other ways. One of the major claimed advantages of MVVM is that due to the separation of business data objects from the UI, we can simply change the UI without touching the business objects. It may be true in some circumstances when the changes to the UI are cosmetic changes. In practice, most of the changes to the UI due to business requirements will be functional changes. These changes will almost always require us to change the UI and the data objects together.

Regardless of the criticisms to the MVVM pattern, the ideas of MVVM such as separating the concerns of data from the concerns of the UI, making the Models and the View-Models unit testable are very valuable ideas in software engineering.

In your applications, whether you want to follow the MVVM pattern and how much you want to follow it is a design choice that you need to make. Whatever your decision is, the best Design Pattern is the pattern that fits your application's needs the best.

History

This is the first revision of the article.

License

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


Written By
United States United States
I have been working in the IT industry for some time. It is still exciting and I am still learning. I am a happy and honest person, and I want to be your friend.

Comments and Discussions

 
QuestionDidn t work for me Pin
Aless Alessio14-Dec-15 3:35
Aless Alessio14-Dec-15 3:35 
AnswerRe: Didn t work for me Pin
Dr. Song Li14-Dec-15 4:06
Dr. Song Li14-Dec-15 4:06 
QuestionAvailability of toolkit for visual studio 2013 Pin
Member 1152400324-Aug-15 22:08
Member 1152400324-Aug-15 22:08 
QuestionQuestion from a student Pin
Member 1067213917-Mar-14 10:19
Member 1067213917-Mar-14 10:19 
QuestionVS2010 Best Practice Pin
Jeremy Bradshaw17-Feb-14 5:21
Jeremy Bradshaw17-Feb-14 5:21 
AnswerRe: VS2010 Best Practice Pin
Dr. Song Li17-Feb-14 15:36
Dr. Song Li17-Feb-14 15:36 
QuestionHere I used to implemet very basic level Pin
prageeth.madhu21-Jan-13 3:37
prageeth.madhu21-Jan-13 3:37 
click
GeneralMy vote of 5 Pin
dineshkaruppaiyah4-Jan-13 22:15
dineshkaruppaiyah4-Jan-13 22:15 
GeneralMy vote of 5 Pin
blpandya10-Dec-12 7:06
blpandya10-Dec-12 7:06 
GeneralMy vote of 5 Pin
hadeed14724-Dec-12 4:39
hadeed14724-Dec-12 4:39 
GeneralRe: My vote of 5 Pin
Dr. Song Li4-Dec-12 4:51
Dr. Song Li4-Dec-12 4:51 
GeneralMy vote of 5 Pin
Arun_angel6-Nov-12 1:24
Arun_angel6-Nov-12 1:24 
QuestionMy Vote of 5 Pin
udayakumarSubramanian25-Sep-12 16:20
udayakumarSubramanian25-Sep-12 16:20 
AnswerRe: My Vote of 5 Pin
Dr. Song Li25-Sep-12 16:58
Dr. Song Li25-Sep-12 16:58 
GeneralMy vote of 5 Pin
ericngando10-Jan-12 5:14
ericngando10-Jan-12 5:14 
QuestionWhat is the purpose of SB.Remove in your code ? Pin
Philippe Mori5-Jan-12 3:00
Philippe Mori5-Jan-12 3:00 
AnswerRe: What is the purpose of SB.Remove in your code ? Pin
Dr. Song Li5-Jan-12 3:39
Dr. Song Li5-Jan-12 3:39 
GeneralRe: What is the purpose of SB.Remove in your code ? Pin
Philippe Mori5-Jan-12 3:51
Philippe Mori5-Jan-12 3:51 
GeneralRe: What is the purpose of SB.Remove in your code ? Pin
Dr. Song Li5-Jan-12 3:59
Dr. Song Li5-Jan-12 3:59 
QuestionVery bad idea to uncheck a whole group of exceptions. Pin
Philippe Mori5-Jan-12 2:47
Philippe Mori5-Jan-12 2:47 
AnswerRe: Very bad idea to uncheck a whole group of exceptions. Pin
Dr. Song Li5-Jan-12 3:40
Dr. Song Li5-Jan-12 3:40 
GeneralMy vote of 2 Pin
ShlomiO23-Dec-11 23:11
ShlomiO23-Dec-11 23:11 
GeneralRe: My vote of 2 Pin
Dr. Song Li26-Dec-11 17:21
Dr. Song Li26-Dec-11 17:21 
GeneralRe: My vote of 2 Pin
Deb Kumar Ghosal28-Jun-12 20:54
Deb Kumar Ghosal28-Jun-12 20:54 
GeneralMy vote of 5 Pin
Vinod Satapara12-Dec-11 2:26
Vinod Satapara12-Dec-11 2:26 

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.