0 Replies - 2528 Views - Last Post: 30 August 2012 - 08:45 PM

#1 Amrykid  Icon User is offline

  • 4+1=Moo
  • member icon

Reputation: 148
  • View blog
  • Posts: 1,589
  • Joined: 16-December 08

Project Crystal - WPF/WP7 - MVVM/ReactiveUI-like/Misc Framework

Posted 30 August 2012 - 08:45 PM

Introduction

Hello all, I am back with another little projects which may or may not help out other WPF devs. Coming off of Hanasu, I have become disgusted by the kind of code I was writing. With Hanasu 2, I wanted to do MVVM which seemed to be the new thing these days. It meant I could keep all my logical code away from the view (and it's code). Over the years, I've been reading the works of Sacha Barber (and his MVVM framework, Cinch), Josh Smith and a few others. Reading their stuff, I began to get an idea of what MVVM was.

I don't know about you but, when I need to learn something, I learn it once and write something so I won't have to remember it. It got me through pre-calc, it applies here. So I set off on implementing the MVVM side of Crystal. The first thing that was implemented, obviousily is the view model. In Crystal, there are two classes for view models: BaseViewModel and DependencyBaseViewModel. Both implement INotification Property Changed which is necessary for real-time data binding. Another neat thing about them is that they handle firing the propertychanged event and storing variables for you like so:
using Crystal.Core;
public class SampleViewModel: BaseViewModel
{
    public object SomeProperty
    {
        get { return (object)this.GetProperty("SomeProperty"); }
        set { this.SetProperty("SomeProperty", value); }
    }
}


Imports Crystal.Core
Public Class SampleViewModel
  Inherits BaseViewModel

  Public Property SomeProperty As Object
    Get
      Return Me.GetProperty("SomeProperty")
    End Get
    Set (ByVal value As Object)
      Me.SetProperty("SomeProperty", value)
    End Set
  End Property
End Class



Of course, the BaseViewModel has some more advanced features which will be revealed later once I get a chance to fully test them. Anyway, the next part is the Models (the first M in MVVM). Models are your structs or classes that contain data, for example, an email could be a Model. The email server could act like the ViewModel while the View itself would be your email client/website. Models, that inherit from BaseModel, get some of the same fancy features as BaseViewModel (Get/SetProperty), along with more such as IDataErrorInfo (which is incomplete at the time of writing) for displaying conflicts with the model on your GUI. There is also a subclass of BaseModel called EditableBaseModel which implements IEditableObject so that changes to the model can be reverted if needed.

Commands
Now that I had the base framework down, I needed to be able to execute code without adding code to the view (Window, or controls)'s code behind. WPF and Silverlight have an interface called ICommand. Yeah, you guessed it. It executes code when activated. Most controls that inherit from ButtonBase have a Command property. Just define a property that returns a ICommand and bind to it. Bam, it executes said command when the button is clicked. I was unaware at the time that RelayCommand and DelegateCommand existed, so I wrote my own classed called CrystalCommand which gets the job done. The nice thing about them is that they bind to your ViewModel/Model's propertychanged event and will activated/deactived based on the function you defined. For example, you can define a CrystalCommand that can only execute when 'SomeProperty' is not null (Nothing in VB.NET).

For the most part, only button(base) controls and menuitems can execute commands. What if I wanted to have a command execute when something was double-clicked on, say, a ListView? Well, Micrososft has released a library called System.Windows.Interactivity (formerly, Micrososft.Expression.Interactivity?) and it was sort of hard to come by. I did not want to bog down the main library with non-GAC references so whenever I reference a non-GAC reference, I add it to Crystal.Extras. The Extras lib has a special trigger (from the Interactivity lib, not the standard Triggers) called EventToCommand. It fires a command when an event is trigger, obviously. So that takes care of executing commands. But there's more!

Messaging
Sacha's Cinch has something called a Mediator which allows for multiple viewmodels to communiate without having a direct reference to each other. It sounded like a good idea and I've toyed around with something similar before. The notifications in Hanasu used some of my earlier code. My latest IRC library uses a slightly improved version (even though it was written before Hanasu. YAY for code reuse!)

I grabbed my notifications code and rewrote it. It is called Messenger. Its fast and easy to use. BaseViewModel implements an interface called IMessageReceiver which contains one function: ReceiveMessage. The Messenger (class) fires this method whenever a registered ViewModel (all view models automatically register) gets a message. BaseViewModel implements that method and makes it 'virtual' (Overridable in VB.NET) so that users can get down to the dirt with it. I was happy with this method at first but I wanted a simple, cleaner way to do it. That was when I came up with the idea (or did I take it from Cinch as well) to add an attribute to a method. The method would fire when a message with the correct message string is received:
        [MessageHandler("ClientAdded")]
        public void HandleClientAdded(object data)
        {
            Clients.Add(new Client() { Fullname = data.ToString() });
        }



Whenever the message, "ClientAdded" is broadcasted, this method would fire along with the message's payload. That is a very asynchronous way to do it but what about doing it synchronously? Well, the Messenger class has a method called WaitForMessage which blocks until a message matching the message string is received. It can also time out after a certain time limit. Once you're ready to receive messages, your other VM (View Models) can push them back. Using Messenger.PushMessage, you can broadcast a message and its data to all view models.

Localization

I don't know about you but when I write applications, I would like to have them translatable from the start. Especially when I started using MVVM. The standard .resx method was not for me. I can go through the story of how I did what I did but I'll save that for another time. Long story short, I use .INI files for localizing.
[locale]
; Provide locale information to the Crystal localization manager.
Name = English
EnglishName = English
IetfLanguageTag = en-US
[data]
; Define key/value pairs that represent text used in an application.
FirstNameHeader = First Name
LastNameHeader = Last Name
IDHeader = ID
CustomerTabHeader = Customers
AddCustomerToolTip = Add a new customer to the customer list.
CancelAddCustomerToolTip = Cancel this operation and close this dialog.
AcceptAddCustomerToolTip = Accept the entered information and close this dialog.



Crystal has a class called the LocalizationManager which is in charge of localizing your applications. Use its ProbeDirectory function to read your .ini files. If you do it early enough, you're application will have the correct (localized) text without 'changing' in front of the user. You can also call its SwitchLocale method as well. The manager has a function called GetLocalizedValue, which as you guessed, gets the correct value for the current Culture from your .ini files.

Another neat thing is the CrystalLocalizedValueMarkupExtension which calls GetLocalizedValue for you so you do not have to go to into code and code your text. As an added bonus, if the locale is switched (using the SwitchLocale function), all text using the markup extension will automatically update to the new localized value.

Services

Another neat feature, which I give credit to Sacha for this idea, is services. They allow you define things such as message boxes, custom file dialogs, errog loggers seperate from your viewmodels. Again, you do not need a direct reference to them. You simply call it like so:
//Taken from Hanasu 2
ServiceManager.Resolve<IMessageBoxService>().ShowMessage("Connection Error", "Unable to stream from station:" + Environment.NewLine + ex.Message);



The above code finds the implementation of IMessageBoxService in Hanasu via MEF (manual reflection coming later) and uses it to show a message. My MessageBox service is defined like this:
namespace Hanasu.Services
{
    [Export(typeof(IMessageBoxService))]
    public class HanasuMessageBoxService: IMessageBoxService
    {
        public void ShowMessage(string title = "Title", string message = "Message")
        {
            MessageBoxWindow mbw = new MessageBoxWindow(title, message, System.Windows.MessageBoxButton.OK);
            mbw.Owner = Application.Current.MainWindow;
            mbw.ShowDialog();
            mbw.Close();
        }

        public bool? ShowOkayCancelMessage(string title, string message)
        {
            throw new NotImplementedException();
        }
    }
}



Misc

Early on, I wanted to implement something similar to Rx and ReactiveUI. Crystal came pretty close to the general idea. I defined something called Tasks which is not finished but they work similar to Rx and are pretty close to my 'Carbon' idea from a while ago. I'll speak more on this later.

Crystal has some other useful features. For example, how do you close a window from a view model? Easy. Use the ViewModelOperations class. It has a handle method which finds the current window by reference using the current view model. You can optionally define a dialog result as well.

Another thing is the IsMainWindow attached property which Crystal defines. Basically, it automatically sets the said window as the Application.Current.MainWindow. This is useful for making dialog boxes (as shown above).

Lastly, I have ported a decent chunk of Crystal to WP7 (relatively easy to get it to Desktop Silverlight) for use in a possible future project.

TL;DR

I have created a library that serves as a framework for my future applications and I wanted to share it with everyone. It has features for validation, localization, MVVM and more!

You can find the source here with binaries coming soon. Also, for a sneak peak of Hanasu 2 which uses Crystal heavily.

Posted Image

This post has been edited by Amrykid: 30 August 2012 - 10:11 PM


Is This A Good Question/Topic? 1
  • +

Page 1 of 1