Click here to Skip to main content
14,365,158 members

A Real MVVMLight Example

Rate this:
5.00 (16 votes)
Please Sign up or sign in to vote.
5.00 (16 votes)
1 Mar 2017CPOL
This is a more comprehensive example of how to implement a project with MVVMLight


I have worked on several projects that have used MVVMLight, but never started one from scratch. It can be very different working on a project using a technology and having to start from scratch. So I started looking around for documentation and samples, and there are so very few, and none particularly comprehensive.


The project I was working on is the front end for an intrument, so it is not like most business applications in that it is not database driven. Not only that but they wanted to be able to accomodate different instruments. That means that the front end should make as few assumptions about what the screens should be as possible. Part of supporting this design is using the concept that a ViewModel knows the Models it needs. It gets these models by using the SimpleIoc to obtain the models. That way the backend can provide the right model for the instrument. Also, the backend is responsible for registering the right class for an interface with the SimpleIoc. Another class that controls the navigation. That way, if an intrument requires a very different screen tree, then all that is required is for the backend to instantiate the right instance of the IDisplayController and register it using SimpleIoc.


Like so many other people, when I started a new WPF project, I had an issue with infrastructure since the tools that are provided by Microsoft as part of the Visual Studio. When creating WPF project I have found that I need support for simple stuff like RelayCommand or DelegateCommand (Prism), and a base view model for INotifyPropertyChanged. On previous project I started from scratch I just created the helper classes I needed. There are other things that MVVM Light provides, and would have helped create more maintainable projects. I have created project with assumptions, but as the projects evolved, they become more complicated and thus harder to maintain. I was again starting from scratch and after hearing the requirements, and with my previous experience, decided really should use a framework this time: rhere are two that are generally used: MVVM Light, and Prism. I have worked with both, and decided that Prism was more of a heavyweight, and MVVM Light would be more than adequate for the application.

The application I was building was not like most applications because it was the front end for an instrument and they wanted a lot of flexibility because the instrument could be different, requiring a different tree of screens. I was obviously going to use the RelayCommand and the ViewModelBase, but I also wanted to depend heavily on the SimpleIoc.

Because of these requirements, the top level design is probably very different from what I would have created if was doing something that was more database-driven. Thus it might not be what somebody else would want to use as a template when creating their application. It also only demonstrates some of the features of MVVM Light. I use the following in this project

  • ViewModelBase
  • SimpleIoc
  • DIspatchHelper

Another useful functionality provided by MVVM Light is Messenger. I had previously worked with Messenger, and I do like the ability to have the decoupling provided by Messenger or the EventAggregator (Prism), but I am sure they come with the performance penalty, and are also harder for somebody to come in cold and work with. I considered using Messenger for a few things, but was able to replace it with simple events. If I do think of someplace where Messenger makes sense, I will include it in the sample.


This application is divided into a number of different projects with the idea of encapsulating functionality, and reducing dependencies:

Interfaces: Contains interfaces and some classes to support communications between the backend and the UI.

ApplicationResources: Contains graphics elements used by the UI, such as icons and jpeg images.

Backend: This simulates the code that would be the middleware.

CommonUI: The code that could be reused for other projects, such as controls, converters, and behaviors.

Startup: The project that initializes the applications and isolates the UI from the backend.

UI: Contains the MainWindow, UserControl Views and ViewModels and Simulators to give the UserControls something to show in Design Mode.

Image 1


There is a project in the solution that is used only for startup. The reason is that this way the UI project and backend project do not need references to each other. The only responsibility of startup project is for initializing the UI and the backend, and coordinating the initialization.

Both the UI and backend have StartUp classes to initialize the front end and backend. Both classes have and Initialize method to actually start the process. The backend has an Initialized event which is used to indicate to the Startup project that the models initially required by the frontend. When the startup project is ready, and has subscribed to the Initialized event, it calls the backend Initialize method. When the Initialized event is observed, then the startup calls the frontend Initialize method.

public partial class App : Application
   Controller.StartUp _controllerStartUp; protected override void OnStartup(StartupEventArgs e)
      _controllerStartUp = new Controller.StartUp();
      _controllerStartUp.Initialized += (sender, args) =>


As can be seen, all that the StartUp project needs to know about the frontend and backend is how to initialize them.


This project is largely made up of interfaces, but also includes some classes that are needed to communicate between the front end and the back end. There is a folder for each type:

ViewModel Interfaces: The interface for each ViewModel is actually empty because it is used back end to tell UI project which screen to display. The UI project uses this interface for finding the associated ViewModel. This means that there is only one ViewModel derived from each interface. The backend is responsible for the navigation between screens. The classes for all the ViewModels are in the UI project.

Model Interfaces: The ViewModels depend on data from the backend. The Model interfaces are used by the ViewModels using SimpleIoc to find the data that is required by the ViewModel. The ViewModels know the data that they require and this is represented by the Model interfaces. Generally, the Model classes are part of the backend…the backend has to instantiate and register these Model classes before requesting that a screen be displayed that requires the Model.

There is actually on Model class that is defined in the interfaces project for button, including the button text and the method to execute when the button is pressed. This is a simple class for passing information and requires no special logic.

Event Argument Classes: There are a number of events in the Models that will notify the ViewModel that there has been some sort of action that is required, and vice versa. The class for the event argument has to be known to both the Model (which is general defined in the backend) and the ViewModel (which is defined in the UI project).

Enums: There are a number of enumerations that are used by both the UI project and the backend.

ClassLocator: The ClassLocation is a class that wraps the SimpleIoc. Often the ServiceLocator is used with the SimpleIoc, but the ServiceLocator class is considered obsolescent by Microsoft. To eliminate the need for projects associated with the backend to have a reference to the MVVM Light projects, this ClassLocation class provides an interface to any methods that are required. Since this class is used for all locator services, it is possible to change from the default SimpleIoc.

public static class ClassLocator
   public static ISimpleIoc Instance => SimpleIoc.Default; public static TClass GetInstance<TClass>() 
         where TClass : class => Instance.GetInstance<TClass>(); 
   public static object GetInstance(Type type) => Instance.GetInstance(type);

   public static void Register<TClass>(Func<TClass> action) 
         where TClass : class => Instance.Register<TClass>(action) ;


If you are familiar with the ClassLocator you will notice that not all methods are exposed, these are the only ones are used in the solution.

DisplayController: The DisplayController class is the class used to control which screen is displayed. It has to be available immediately so that the display can be changed when the backend is ready. Therefore the backend is not given responsibility for the creation and registering of this class, which is done when the application is initialized:

public class DisplayController : IDisplayController
   public event EventHandler<ChangeDisplayEventArgs> ChangeDisplayEvent; 

   public void ChangeDisplay(Type viewModelInterface, bool createNew = false)
      ChangeDisplayEvent?.Invoke(this, new ChangeDisplayEventArgs(viewModelInterface, createNew)));

   public void ChangeDisplay(Type viewModelInterface, DisplayActionTypes displayAction,
         bool createNew = false)
      DispatcherHelper.CheckBeginInvokeOnUI(() =>
            new ChangeDisplayEventArgs(viewModelInterface, displayAction, createNew)));

   public void RevertDisplay()
            ChangeDisplayEvent?.Invoke(this, new ChangeDisplayEventArgs()));

There is an event that the MainViewModel will subscribe to for notification that a new ViewModel should be displayed, and a number of methods that the backend can use to change the displayed screen. Both the MainViewModel and the backend can find the instance of this class using the SimpleIoc.

The class keeps a stack that can be used to push a ViewModel onto when it is replaced so that it is easy to return back to a previous display without knowing what that display was. There are three methods currently available for cause the screen to be changed: Revert to a previous screen, Change the Display using the default display action, which is to push the current display onto the stack and replace it with the new display type, and a method that allows the specification of what is to be done with the current display (including replacing it with the one on the top of the stack). There is also an argument to specify that the ViewModel is to be created new, which also means that it will be destroyed when the UI is finished with it (this only happens when the display is not pushed onto the stack when replaced). A lot of the functionality is in the ChangeDisplayEvent event’s arguments class:

public class ChangeDisplayEventArgs
   public static readonly Dictionary<Type, Type> InterfaceTypeDictionary
      = new Dictionary<Type, Type>(); 

   public ChangeDisplayEventArgs(Type displayViewModelInterface, bool createNew = false) :
         this(displayViewModelInterface, DisplayActionTypes.Replace, createNew) { } 

   public ChangeDisplayEventArgs(Type viewModelInterface, DisplayActionTypes displayAction,
         createNew = false)
      ViewModelInterface = viewModelInterface;
      Debug.Assert(displayAction != DisplayActionTypes.PopPreviousDisplay || viewModelInterface == null,
            "PopPreviousDisplay requested with a specification of DisplayViewModelInterface");
      DisplayActionType = displayAction;
      CreateNew = createNew;

   /// <summary>
   /// This is used to just pop the previous display
   /// </summary>
   public ChangeDisplayEventArgs()
      DisplayActionType = DisplayActionTypes.PopPreviousDisplay;

   public Type ViewModelInterface { get; } 
   public DisplayActionTypes DisplayActionType { get; } 
   public bool CreateNew { get; }


The back end needs to initialize and register the Models to drive the User Interface. It also uses the instance of the DisplayController class to change the screen that the user sees. I could have used Messenger for the communications, but something like Messenger has a lot of overhead and, from what I understand, use should be minimized. Therefore events are used heavily for to communicate to the User Interface.

Simple Example of Mode with Command Event Handler to Change Display

The code to be executed when the user does an action such as a button click uses the EventHandler<CommandArgs>. Currently the CommandArgs is fairly simple, having a single optional parameter which means that if a Command includes a Command Parameter, it can be passed to the executing code:

public class CommandArgs
   public CommandArgs(object commandParameter = null)
       CommandParameter = commandParameter;

   public object CommandParameter { get; }


A simple case of where a Model has to handle a command is the AboutModel:

public class AboutModel : IAboutModel
   private readonly IDisplayController _displayController; public AboutModel()
      _displayController = ClassLocator.GetInstance<IDisplayController>();
      Debug.Assert(_displayController != null,
            IDisplayController not found for AboutModel");
   public void AboutOkCommand(object sender, CommandArgs a)

The AboutModel inherits from the IAboutModel so that the ViewModel using it can use the ClassLocator to find the instance once the In this case the Model needs to get an instance of the IDisplayController using the ClassLcator so that it can change the display. This can be seen in the constructor. The AboutOKCommand follows the interface EventHandler<CommandArgs>. It uses the RevertDisplay method of the DisplayControl to pop the previous display ViewModel from its ViewModel stack and use that in the binding for active ViewModel.

BottomBarMenu: Forcing an Update in the UI

The BottomBarModel controls the Menu Button at the bottom right of the MainWindow and the message to the left:

public class BottomBarModel : IBottomBarModel
   private readonly IDisplayController _displayController; public BottomBarModel()
      _displayController = ClassLocator.GetInstance<IDisplayController>();
      Debug.Assert(_displayController != null, "IDisplayController not found for BottomBarModel");
      UserMessage = "Initializing...";
      MessageType = MessageTypeEnum.Information;

   public void MenuCommand(object sender, CommandArgs a)
      DisplayActionTypes.PushPreviousDisplay, true);

   public void UpdateUserMessage(string message, MessageTypeEnum messageType)
             new UpdateUserMessageEventArgs(message, messageType));
      UserMessage = message;
      MessageType = messageType;

   public event EventHandler<UpdateUserMessageEventArgs> UpdateUserMessageEvent;

   public string UserMessage { get; private set; }

   public MessageTypeEnum MessageType { get; private set; }

This also has a method to handle the button press of the “Menu” button, therefore this class also needs the instance of the DisplayController. In this case the method uses the ChangeDisplay with the IMenuViewModel type, and the bool is set to true to create a new instance of the class derived from the IMenuViewModel.

The message displayed to the user is changed by the back end by calling the UpdateUserMessage which has arguments for the message and the message type (which will change the background). This updates the properties and triggers the UpdateEvent for the UI.

NOTE: I could have combined the DisplayController and BottomBarModel, but thought these were better encapsulated in different interfaces. They could still be combined.


The MenuModel, which derives from IMenuModel, is an interesting case because it allows a list of button information to be passed to the UI, which currently includes the label for the button and action to perform when the button in pressed.

public class MenuModel : IMenuModel
   private readonly IDisplayController _displayController; public MenuModel()
      _displayController = ClassLocator.GetInstance<IDisplayController>();
      Debug.Assert(_displayController != null, 
            "IDisplayController not found for MenuModel"); 
      MenuButtonCommands = new List<IButtonModel>()
         new MenuButtonModel ("Home", (s, a) =>

         new MenuButtonModel ("About", (s, a) =>
                DisplayActionTypes.PushPreviousDisplay, true);

   public IList<IButtonModel> MenuButtonCommands { get;}

This is generally the pattern that was planned for interfacing to all the screens so that the backend could specify the contents of the screen.

The UI project

The UI project consists mostly of the Views and ViewModels, but also has the simulators for the Models that ensure that there are no errors when creating the design view of the UserControl files, and also used to provide properties that allow sample design elements to be visible on the UserControl design views. There is also another critical element in this the extension method that finds the class in the UI project that implements the interface that the backend uses to tell the UI the screen to display:

public static BasicViewModelBase ViewModel(this ChangeDisplayEventArgs changeDisplayEventArgs)
   BasicViewModelBase viewModel;
   var viewModelInterface = changeDisplayEventArgs.ViewModelInterface;
   var dictionary = ChangeDisplayEventArgs.InterfaceTypeDictionary; 

   if (changeDisplayEventArgs.CreateNew)
      if (dictionary.ContainsKey(viewModelInterface))
         viewModel = (BasicViewModelBase)Activator
         viewModel.DisposeOfAfterUse = changeDisplayEventArgs.CreateNew;
         var names = Assembly.GetExecutingAssembly().GetTypes().Select(i => i.Name); 
         var displayViewModelClass = Assembly.GetExecutingAssembly().GetTypes()
            .FirstOrDefault(i => i.IsClass && i.GetInterfaces().Contains(viewModelInterface));
         Debug.Assert(displayViewModelClass != null,
            $"Could not find a Type that is derived from the interface {viewModelInterface.Name}");
         viewModel = (BasicViewModelBase)Activator.CreateInstance(displayViewModelClass);

               .Add(viewModelInterface, displayViewModelClass);
         viewModel.DisposeOfAfterUse = changeDisplayEventArgs.CreateNew;
      viewModel = (BasicViewModelBase)
   return viewModel;

An extension method is used with the ChangeDisplayEventArgs because this code only works if the ViewModel classes to search to find the class that inherits from the interface used to specify the screen. This means that this design is only good for smaller applications where all the ViewModels are in the same assembly. It is possible to write code to search for multiple assemblies.

Currently there is some untested code in the ClassLocator class. A couple of methods were added to the Class Locator that do not exist in the SimpleIoc: The first ischeck if an instance exists SimpleIoc code used to check if an instance exists for a type—The SimpleIoc only has a generic method for checking. The second is to register an instance passing the lookup as a type instead of a generic.

To improve performance, once a class is found that matches a specified interface, the association is saved in a static dictionary. If the association is not found, then reflection is used to find the classes in the assembly and search for the one that inherits from the interface.


All the ViewModels inherits from UiViewModelBase which inherits from the MVVM Light ViewModelBase. I could have used and interface, but this way do not have to reference both the ViewModelBase and an interface. The reasons I use the ViewModelBase is its support of INotifyPropertyChanged, and overriding the CleanUp method. The UiViewModelBase currently has a property DisposeOfAfterUse that is used to indicate that this ViewModel is to be disposed of as soon as finished with current instance.


The following is the class used for the AboutVIewModel:

public class AboutViewModel : UiViewModelBase, IAboutViewModel
   private IAboutModel _aboutModel; public AboutViewModel()
      if (this.IsInDesignMode)
         _aboutModel = new AboutModelSimulator();
         _aboutModel = ClassLocator.GetInstance<IAboutModel>();
         Debug.Assert(_aboutModel != null, " IAboutModel not found for AboutViewModel");

   public string Version =>  System.Reflection.Assembly.GetExecutingAssembly()

   public string Copyright => GeneralHelpers.GetCopyright(); 

   public RelayCommand OkCommand => new RelayCommand(() =>
         _aboutModel.AboutOkCommand(this, new CommandArgs())); 

   public override void Cleanup()
      _aboutModel = null;

This class has two properties that are used to display data (Version and Copyright), and a property for an ICommand interface. Since Version and Copyright will not change, there is no need to implement INotifyPropertyChanged for these two properties. The OkCommand property accesses the AboutOkCommand method of the AboutModel. No need to worry about changes in the Model since this happens with the call to the method.


The ViewModel for the main window is as follows:

public class MainViewModel : UiViewModelBase
   private readonly IDisplayController _displaController;
   private readonly Stack<UiViewModelBase> _primaryDisplayStack
            = new Stack<UiViewModelBase>(); private UiViewModelBase _PrimaryViewModel;

   private string _userMessage;

   private IBottomBarModel _bottomBarModel;

   private MessageTypeEnum _messageType; public MainViewModel()
      _displayController = ClassLocator.Instance.GetInstance<IDisplayController>();
      Debug.Assert(_displayController != null, nameof(IDisplayController)
            + " not found for " + GetType().Name);
      _displayController.ChangeDisplayEvent += DisplayChangeEventHandler;
      PrimaryViewModel = new SplashScreenViewModel { DisposeOfAfterUse = true }; 
      if (this.IsInDesignMode)
         _bottomBarModel = new BottomBarModelSimulator();
         _bottomBarModel = ClassLocator.GetInstance<IBottomBarModel>();
         Debug.Assert(_bottomBarModel != null, "IBottomBarModel not found for MainViewModel");
         _bottomBarModel.UpdateEvent += UpdateUserMessageEventHandler;

      UserMessage = _bottomBarModel.UserMessage;

      MessageType = _bottomBarModel.MessageType;


   private void UpdateUserMessageEventHandler(object sender, UpdateEventArgs e)
      UserMessage = _bottomBarModel.UserMessage;
      MessageType = _bottomBarModel.MessageType;

   private void DisplayChangeEventHandler(object sender, ChangeDisplayEventArgs changeDisplayEventArgs)
      //Should not be keeping (or using) previous display and disposing
      switch (changeDisplayEventArgs.DisplayActionType)
         case DisplayActionTypes.PopPreviousDisplay:
            PrimaryViewModel = _primaryDisplayStack.Pop();
         case DisplayActionTypes.PushPreviousDisplay:
      if (PrimaryViewModel.DisposeOfAfterUse &&
               !(changeDisplayEventArgs.DisplayActionType == DisplayActionTypes.PushPreviousDisplay))
      PrimaryViewModel = changeDisplayEventArgs.ViewModel();

   public UiViewModelBase PrimaryViewModel
      get { return _PrimaryViewModel; }
      set { Set(ref _PrimaryViewModel, value); }

   public string UserMessage
      get { return _userMessage; }
      set { Set(ref _userMessage, value); }

   public MessageTypeEnum MessageType
      get { return _messageType; }
      set { Set(ref _messageType, value); }

   public RelayCommand MenuCommand => new RelayCommand(() =>
            _bottomBarModel.MenuCommand(this, new CommandArgs())); 

   public override void Cleanup()
      _displayController.ChangeDisplayEvent -= DisplayChangeEventHandler;
      _bottomBarModel.UpdateEvent -= UpdateUserMessageEventHandler;
      while (_primaryDisplayStack.Count > 0)

There are several functions of this ViewModel:

  • Monitoring the ChangeDisplayEvent of the IDisplayController for updates to the PrimaryViewModel property, which changes the View used in the main part of the MainWindow.
  • Monitoring the Update event of the IBottomBarModel for updates of the user message and type for the message display at the bottom left of the MainWindow. When the Update event of the IAboutWindow fires, the UserMessage and MessageType properties are updated.
  • Providing an ICommand property for the Menu button at the bottom right of the MainWindow. The MenuCommand of the IBottomBarModel is executed when the ICommand is executed.

The constructor gets instances of the IDisplayController and IBottomBarModel using the ClassLocator in the Interfaces project, and subscribes to the events of these interfaces. There is also a test whether the ViewModel is in design mode using the MVVM Light IsInDesignMode property. If it is in design mode then the IBottomBarModel simulator is used instead of using instance using the ClassLocator.

There is also a CleanUp virtual method from the ViewModelBase of MVVM Light. In this code it can be seen that it is used to unsubscribe from the events subscribed to in the contructor, and remove any references.

The functionality of the ViewModel constructor and CleanUp method will be very similar for most of the ViewModels in this design.

When any of the properties are updates, the MVVM Light Set method is used to execute the PropertyChangedEvent of INotifyPropertyChanged.


There is the MainWindow which is derived from window and the Views which are UserControls.


The Main window has a ContentPresenter control whose content is bound to the PrimaryViewModel property of the DataContext. The object Type of this property determines which UserControl is displayed:

<Window x:Class="UI.MainWindow"

        xmlns="<a href=""></a>"
        xmlns:x="<a href=""></a>"
        xmlns:d="<a href=""></a>"
        xmlns:mc="<a href=""></a>"
        <viewModel:MainViewModel />
            <ColumnDefinition Width="8*" />
            <ColumnDefinition Width="*" />
            <RowDefinition />
            <RowDefinition Height="Auto"

                           MinHeight="40" />
        <ContentPresenter Grid.ColumnSpan="2"

                          Content="{Binding PrimaryViewModel}" />
        <Border Grid.Row="1"


                Background="{Binding MessageType, 



            <TextBlock Margin="10,0"


                       Text="{Binding UserMessage}" />
        <Button Grid.Row="1"



                Command="{Binding MenuCommand}"

                Content="Menu" />

There are also the control to display the status message to the user, and a button to press to get to the menu UserControl at the bottom of the Window. The window also has the DataContext specified as the MainViewModel

The association of the ViewModel to the View is done with a DataTemplate that has the DataType specified and whose content is the UserControl that should be associated with that DataType:

<DataTemplate DataType="{x:Type viewModel:AboutViewModel}">
    <view:AboutView />
<DataTemplate DataType="{x:Type viewModel:MenuViewModel}">
    <view:MenuView />
<DataTemplate DataType="{x:Type viewModel:SplashScreenViewModel}">
    <view:SplashScreenView />

Screen Shots

Image 2

Initial Screen with message to user of "Initializing" in green background indicating that this is informational message.

Image 3

Scrren after 10 seconds when message had been update to "10 seconds have passed" in yellow background indicating that this is warning message.

Image 4

Screen after "Menu" button at bottom right is clicked. This screen has a set of buttons that allow navigation to other screen in the application.

Image 5

Screen after "About" button is clicked. Clicking the "OK" button brings back the Menu screen buy popping the Menu screen off the stack


The application is pretty simple right now, consisting of only the initial screen, a menu screen, and an about screen. There would have been some additional screens to allow display status, and allow configuration values to be viewed and possibly updated. I plan to add some screen to make this look more like a full fledged application with time, and also to split the UI project into  View and ViewModel project. Suggestions on how to make this a more useful example are welcome. Understand that I have used MVVM Light, but not all the freaturs. Also, if someone can suggest a really good reason to use Messenger, I would like to hear it since I would like to include code for Messenger, but just have not found a good reason yet. 


  • 03/01/2017: Initial Version
  • 03/03/2017: Updated UniformGridItemsControl, and added new Configuraiton screen which shows idea of how to allow user to view and change values that are specified by the Backend.


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


About the Author

Clifford Nelson
Software Developer (Senior) Clifford Nelson Consulting
United States United States
Has been working as a C# developer on contract for the last several years, including 3 years at Microsoft. Previously worked with Visual Basic and Microsoft Access VBA, and have developed code for Word, Excel and Outlook. Started working with WPF in 2007 when part of the Microsoft WPF team. For the last eight years has been working primarily as a senior WPF/C# and Silverlight/C# developer. Currently working as WPF developer with BioNano Genomics in San Diego, CA redesigning their UI for their camera system. he can be reached at

Comments and Discussions

SuggestionOverComplicated Pin
Mr.PoorEnglish10-Mar-17 19:25
memberMr.PoorEnglish10-Mar-17 19:25 
AnswerRe: OverComplicated Pin
Clifford Nelson13-Mar-17 5:49
mvaClifford Nelson13-Mar-17 5:49 
GeneralThat's great but.... Pin
mesta8-Mar-17 5:12
membermesta8-Mar-17 5:12 
AnswerRe: That's great but.... Pin
Clifford Nelson9-Mar-17 5:26
mvaClifford Nelson9-Mar-17 5:26 
BugAppropriate Code? Pin
Mr.PoorEnglish4-Mar-17 20:34
memberMr.PoorEnglish4-Mar-17 20:34 
AnswerRe: Appropriate Code? Pin
Clifford Nelson6-Mar-17 5:50
mvaClifford Nelson6-Mar-17 5:50 

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.

Posted 1 Mar 2017

Tagged as


21 bookmarked