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

WPF: FlipTile 3D

, 21 May 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
WPF: A demo of using the Onyx WPF framework.

Contents

Introduction

I have been working with WPF both for fun and for a job for about two years now, and have followed the evolution of WPF patterns, and I am lucky enough to be close to some of the biggest movers and shakers in the WPF world. It seems the majority of the smart WPF folks out there have settled for a pattern called ModelView-ViewModel (MVVM, for short). This pattern allows a view to be abstracted to a ViewModel, where by the View is passive and simply binds (using WPF Binding) to the associated ViewModel(s). The beauty of the pattern is that if you have done the whole MVVM thing correctly, you should be able to test your UI without the need for the actual UI. You can also test the ViewModels. In fact, you should be able to examine the ViewModels for an application without even seeing the View(s) and know what is going on.

Now, the holy grail of MVVM WPF development would be to have no code-behind at all, and have the View simply bind to a ViewModel. Unfortunately, there are always some things that prevent the beauty of this. For example, how does a ViewModel show a MessageBox or disable a control? It is quite a challenge, but luckily, WPF provides such help, by the use of routed commanding, which allows ViewModels to have commands which the UI controls that support the ICommand interface can run. So what about MessageBoxes/Dialogs (such as OpenFileDialog/SaveFileDialog etc.) and things like that? How does a ViewModel deal with that?

Well, it is not that easy using WPF out of the box, you need to put some effort into it. If you do not, you will end up with a slew of untestable code-behind. Believe me, this can easily happen, and still happens to me on occasions, and it makes me really mad, as I am breaking what I consider to be a nice pattern. GRRR...

So you either have to do quite a birt of custom work yourself, or maybe let someone else put the effort in and use their work. As luck would have it, one of my WPF Disciples buddies, Bill Kempf, has done some nice work on a cool MVVM WPF framework that he is calling Onyx. Bill is not finished with the Onyx framework, and it is evolving, but I just wanted to give it some exposure, and see what it is capable of right now.

So in this article, I am going to talk about what Bill's rather nifty MVVM WPF framework can do. If this interests you, please read on.

Show it

OK, so what does the demo app look like? Well, I did want to do something quite dull, so it would be clear which parts were the Onyx framework and which bits were not, but I just couldn't help myself and had to come up with something that pleased me. So I came up with a little 3D app that lets you view some of the WPF Disciples in 3D space, and flip the 3D to show a larger image of your chosen WPF Disciples, and it will also show you their blog using the .NET 3.5 WebBrowser control.

So here is a screenshot or 6:

Step 1: Click the main button at the top

Step 2: Pick the Disciples.xml file from the DEBUG folder of where you downloaded the code to

Step 3: Enjoy the 3D animation

Step 4: Pick someone

Step 5: Wait for slide in with the chosen person's blog

Step 6: Read their blog if you want to

I am acutely aware that this is a pretty useless app, and it is, I am under no illusions about that. The demo app is not the point, it is a simple demo of what is really important, and that is the actual Onyx framework, so this is what this article is all about really. I think it is my job to try and spread the word about something that will help you, and I think the Onyx framework will help you. Good one Bill. OK, so now, let's get on with the main thrust of the article shall we.

Prerequisites

The only thing you really need to run this code is the Onyx DLL, and .NET 3.5 SP1, which is included in the downloaded code, but if you want to dive into the guts of it, you will also needs Bill's other Open Source project Specificity, which Onyx uses for testing. That is also worth a look. It is not often I am bowled over by someone's code, but I really like both of Bill's Open Source projects.

Getting into the Onyx Framework

This section will go into how Onyx helps you out in your day to day WPF MVVM development. It it worth pointing out that I am not the author of Onyx, so anything I mention here is by way of me looking through the source code that Bill wrote.

Setting Up the ViewModel

One requirement Onyx does have is that you must associate a particular View with its associated ViewModel using an Attached DependencyProperty. This is easily done as follows:

<Window x:Class="FlipTile3D.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:onyx="http://schemas.onyx.com/2009/fx/presentation"
    xmlns:local="clr-namespace:FlipTile3D"
    onyx:View.Model="{x:Type local:MainWindowViewModel}">

What is actually happening here? Well, quite a lot actually. The onyx:View.Model attached DP is declared as:

public static readonly DependencyProperty ModelProperty =
    DependencyProperty.RegisterAttached(
        "Model",
        typeof(object),
        typeof(View),
        new FrameworkPropertyMetadata(null, 
            FrameworkPropertyMetadataOptions.Inherits, 
            OnModelChanged, CoerceModel));

/// <summary>
/// Called when the <see cref="ModelProperty"/> attached dependency property is 
/// changed on the <paramref name="source"/>.
/// </summary>
/// <param name="source">The source object.</param>
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> 
/// instance containing the event data.</param>
private static void OnModelChanged(DependencyObject source, 
                    DependencyPropertyChangedEventArgs e)
{
    if (source.AsFrameworkObject() == null)
    {
        return;
    }

    object model = e.NewValue;
    if (model == null)
    {
        return;
    }

    if (DependencyPropertyHelper.GetValueSource(source, 
          ModelProperty).BaseValueSource == BaseValueSource.Local &&
        DependencyPropertyHelper.GetValueSource(source, 
          FrameworkElement.DataContextProperty).BaseValueSource != BaseValueSource.Local)
    {
        source.SetValue(FrameworkElement.DataContextProperty, model);
    }
}

What Onyx does behind the scenes when you set the onyx:View.Model Attached DP changes is that the associated View (the source of the onyx:View.Model Attached DP) will have its DataContext property set to the ViewModel that is set to the onyx:View.Model Attached DP value.

This one property is enough to set up all the binding between the View and its associated ViewModel.

Commanding Support

Onyx makes use of RoutedCommands which are standard WPF mechanisms. What RoutedCommands allow you to do is separate the command and the implementation. For example, it is possible to have a ViewModel expose RoutedCommands which are then used to bind to by the View, and the command implementation will then be done in the ViewModel, again allowing for clean testable code.

Here is how Onyx works with commands; all you have to do is expose a Command and define the command implementation within your ViewModel:

public class MainWindowViewModel : ViewModel
{
    #region Ctor
    /// <summary>
    /// Initializes a new instance of the <see cref="MainWindowViewModel"/> class.
    /// </summary>
    /// <param name="view">The view to associate with this instance.</param>
    public MainWindowViewModel(View view)
        : base(view)
    {
        this.CommandBindings.Add(new CommandBinding(OpenDisciplesXMLFileCommand,
            this.OpenDisciplesXMLFileExecuted,
            new CanExecuteRoutedEventHandler(this.OpenDisciplesXMLFileCanExecute)));
    }
    #endregion

    #region Commands
    /// <summary>
    /// The OpenDisciplesXMLFile routed command.
    /// </summary>
    private static readonly RoutedUICommand cmdOpenDisciplesXMLFile =
        new System.Windows.Input.RoutedUICommand(
            "Open Disciples XML File",
            "OpenDisciplesXMLFileCommand",
            typeof(MainWindowViewModel));

    /// <summary>
    /// Gets the OpenDisciplesXMLFile command.
    /// </summary>
    /// <value>The OpenDisciplesXMLFile command.</value>
    public static RoutedUICommand OpenDisciplesXMLFileCommand
    {
        get { return cmdOpenDisciplesXMLFile; }
    }

    /// <summary>
    /// Determines if the OpenDisciplesXMLFileCommand can Execute
    /// </summary>
    private void OpenDisciplesXMLFileCanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
        //Allow command to run
        e.CanExecute = true;
    }

    /// <summary>
    /// Handles the execution of the <see cref="MainWindow.OpenDisciplesXMLFile"/> command.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="args">The <see cref="System.Windows.RoutedEventArgs"/> 
    /// instance containing the event data.</param>
    private void OpenDisciplesXMLFileExecuted(object sender, RoutedEventArgs args)
    {
        //DO SOMETHING
        //DO SOMETHING
    }
}

So all we then need to do is wire up the View to the exposed ViewModel commands. Here is an example:

<Button x:Name="btnOpenFile" 
    Command="local:MainWindowViewModel.OpenDisciplesXMLFileCommand"/>

That is it. This means the ViewModel has the actual command and its implementation, and the View simply binds to these exposed commands. This also enables Unit Testing, where by the unit tests can execute the ViewModel commands like:

ViewModel x = new ViewModel();
x.SomeCommand.Execute();

ServiceLocator

At its heart, Onyx is a ServiceProvider. Internally, it registers new services, which can then be fetched using the Microsoft System.IServiceProvider interface, which is defined as follows:

namespace System
{
    // Summary:
    //     Defines a mechanism for retrieving
    //     a service object; that is, an object that
    //     provides custom support to other objects.
    public interface IServiceProvider
    {
        // Summary:
        //     Gets the service object of the specified type.
        //
        // Parameters:
        //   serviceType:
        //     An object that specifies the type of service object to get.
        //
        // Returns:
        //     A service object of type serviceType.
        //     -or- null if there is no service object
        //     of type serviceType.
        object GetService(Type serviceType);
    }
}

What happens is that the Onyx View object is used to expose its services, which is translated internally to calling the System.IServiceProvider.GetService() method. Here is how this is done within the Onyx View object:

/// <summary>
/// Gets the service object of the specified type.
/// </summary>
/// <param name="serviceType">An object that
/// specifies the type of service object to get.</param>
/// <returns>
/// A service object of type <paramref name="serviceType"/>.
/// -or-
/// null if there is no service object of type <paramref name="serviceType"/>.
/// </returns>
/// <exception cref="ObjectDisposedException">This instance has been disposed.</exception>
public object GetService(Type serviceType)
{
    if (serviceType == null)
    {
        throw new ArgumentNullException(Reflect.GetField(() => serviceType).Name);
    }

    return this.serviceContainer.GetService(serviceType);
}

Now we know that we can obtain services for a View. What sort of services does Onyx provide? Well, in order to understand that, we need to look into how the Onyx View object gets created in the first place; here is what happens:

/// <summary>
/// Gets the view.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>
/// The <see cref="View"/> associated with the <paramref name="source"/>.
/// </returns>
/// <remarks>
/// If there currently is no <see cref="View"/>
/// associated with the <paramref name="source"/>
/// then a new <see cref="View"/> is created.
/// </remarks>
/// <exception cref="ArgumentNullException">
///    <paramref name="source"/> is null.</exception>
public static View GetView(DependencyObject source)
{
    if (source == null)
    {
        throw new ArgumentNullException(Reflect.GetField(() => source).Name);
    }

    View view = (View)source.GetValue(ViewProperty);
    if (view == null)
    {
        view = new View(source);
        source.SetValue(ViewProperty, view);
        IServiceRegistry container = 
            View.ServiceRegistryActivator.CreateServiceRegistry();
        View.ViewCreated(view, new ViewCreatedEventArgs(view.ViewElement, container));
        container.RegisterServicesFor(view.ViewElement);
        view.serviceContainer = container.CreateServiceProvider();
    }

    return view;
}

It can be seen that this method first examines the Onyx View object to see if it needs to be instantiated, and if the Onyx View object does need creating, that is when all the services are registered. This brings up back to the question of what services are registered and will be available. The key to this lies in looking at the way the ViewCreated event works. Let's see the resultant code that is run when the Onyx View is created.

/// <summary>
/// Called when a <see cref="View"/> is created.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="ViewCreatedEventArgs"/> 
/// instance containing the event data.</param>
/// <remarks>
/// Adds common framework services to the <see cref="View"/>.
/// </remarks>
private static void OnViewCreated(object sender, ViewCreatedEventArgs e)
{
    UIElement uiElement = e.ViewElement as UIElement;
    if (uiElement != null)
    {
        UIElementServices services = new UIElementServices(uiElement);
        services.AddServices(e.ServiceContainer);
    }

    IFrameworkObject frameworkObject = e.ViewElement.AsFrameworkObject();
    if (frameworkObject != null)
    {
        CommonFrameworkElementServices services = 
          new CommonFrameworkElementServices(frameworkObject);
        services.AddServices(e.ServiceContainer);
    }
}

It can be seen that Onyx uses a UIElementServices object, and a CommonFrameworkElementServices object to add services from. Let us now examine those two Onyx objects.

UIElementServices

internal class UIElementServices : ServicesBase, ICommandTarget, 
        IFocusSuggested, IDisplayMessage, ICommonDialogProvider

This Onyx object provides the following services:

  • Focus support
  • MessageBox support
  • OpenFileDialog support
  • SaveFileDialog support

CommonFrameworkElementServices

internal class CommonFrameworkElementServices : ServicesBase, IElementLifetime

This Onyx object provides the following services:

  • Object lifetime support, such as Initilialized/Created/Loaded

For now, all that is important to know is that the Onyx View object exposes services, and that you are able to use these services from a ViewModel. I will now carry on and show you how to use some typical Onyx services.

MessageBox (Service)

One of the things that catches people when they are trying to create a typical WPF MVVM app is that they get part way there, and then something goes wrong in the ViewModel, and they need to tell the user about this. Oh crap. This typically means showing a MessageBox to the user. So you could either create a new MessageBox showing interface on the View and get the ViewModel to talk to the View using this new interface, or you ignore the fact that your ViewModel should be UI agnostic and just let it reference the necessary DLLs and have the ViewModel show a MessageBox to the user.

Both these approaches do work, but the second one just doesn't feel right. The specific interface approach sounds nicer. Luckily, Onyx caters for this approach quite nicely.

To show a MessageBox to the user from the ViewModel, we simply obtain the correct type of service (View implemented interface) and do something with this obtained service within our ViewModel.

Onyx exposes a MessageBox service as IDisplayMessage, which can be used as follows:

var messageBoxService = this.View.GetService<IDisplayMessage>();
messageBoxService.ShowMessage("Welcome to a small Onyx Demo.\r\n\r\n " +
    "Please pick the Disciples.xml file from the debug directory","Onyx demo",
    MessageBoxButton.OK,MessageBoxImage.Information);

Simple, and clean.

Common Dialogs (Service)

Showing common dialogs such as OpenFileDialog/SaveFileDialog etc., is another real concern that could lead to problems, as these are typically done in the code-behind, which is something we are trying to avoid.

Again, Onyx handles these with ease. Let's have a look at how Onyx caters for this. Within Onyx, there is a class called UIElementServices that is used to provide UI services (we touched on this earlier); recall, it looked like this:

internal class UIElementServices : ServicesBase, ICommandTarget, 
         IFocusSuggested, IDisplayMessage, ICommonDialogProvider

It can be seen that one of the Interfaces that the UIElementServices class implements is ICommonDialogProvider, which looks like this:

namespace Onyx.Windows
{
    /// <summary>
    /// Provides factory methods for creating common dialogs.
    /// </summary>
    public interface ICommonDialogProvider
    {
        /// <summary>
        /// Creates the open file dialog.
        /// </summary>
        /// <returns>
        /// A newly created <see cref="IOpenFileDialog"/> instance.
        /// </returns>
        IOpenFileDialog CreateOpenFileDialog();

        /// <summary>
        /// Creates the save file dialog.
        /// </summary>
        /// <returns>
        /// A newly created <see cref="ISaveFileDialog"/> instance.
        /// </returns>
        ISaveFileDialog CreateSaveFileDialog();
    }
}

So it can be seen that Onyx currently caters for OpenFileDialog and SaveFileDialog. How do we use a OpenFileDialog and SaveFileDialog from our ViewModel? Well, it is as easy as this:

var openFileDialogService = 
    this.View.GetService<ICommonDialogProvider>().CreateOpenFileDialog();
openFileDialogService.InitialDirectory = 
    System.IO.Directory.GetCurrentDirectory();
openFileDialogService.Filter = "XML files (*.xml)|*.xml;|All files (*.*)|*.*";

if (openFileDialogService.ShowDialog() == true)
{
    string fileName = openFileDialogService.FileName;
    ....
    ....
    ....
    ....
}

INotifyPropertyChanged Goodness

A typical ViewModel will contain properties that the View will bind to. In order for a View to receive changes in bound values, the ViewModel will typically implement the INotifyPropertyChanged interface (when implemented, the interface communicates to a bound control the property changes on an object), which is usually implemented like this:

// This class implements a simple customer type 
// that implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
    private string customerName = String.Empty;
    public event PropertyChangedEventHandler PropertyChanged;

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

    // The constructor is private to enforce the factory pattern.
    private DemoCustomer()
    {
        companyNameValue = "no data";
    }

    // This is the public factory method.
    public static DemoCustomer CreateNewCustomer()
    {
        return new DemoCustomer();
    }

    public string CompanyName
    {
        get {return this.companyNameValue;}

        set
        {
            if (value != this.companyNameValue)
            {
                this.companyNameValue = value;
                NotifyPropertyChanged("CompanyName");
            }
        }
    }
}

Where binding changes are notified by the use of the INotifyPropertyChanged interface's NotifyPropertyChanged(String propertyName) method. The problem with the standard implementation is that it uses magic strings in the code. Now, there is a potential for these magical strings to get missed when refactoring, or the magic strings could be wrongly cased, or incorrectly spelt, which would mean that the associated bound object would miss a notification of a change in the bound object properties.

Lately, there have been many people working with creating static Reflection lookups, which is typically done using LINQ Expression trees. Onyx is no different; it also uses LINQ Expression trees to assist with the INotifyPropertyChanged interface. What this means is that there are no magic strings. Let us see an example:

public List<discipleinfo> DiscipleInfos
{
    get { return discipleInfos; }
    set
    {
        this.discipleInfos = value;
        OnPropertyChanged(() => DiscipleInfos);
    }
}

Can you see that we are using a LINQ Expression here (which if you dived into Onyx, you would see is declared as an Expression<Func<T>> expression)? Here is what happens within Onyx. As previously stated, Onyx uses a LINQ Expression tree, and does some static Reflection on that to work out the property that is forming part of the Expression tree, and then it fires the normal INotifyPropertyChanged interface NotifyPropertyChanged(String propertyName) method.

/// <summary>
/// Called when a property changes, using static reflection to determine the property.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="expression">Expression that accesses the property that changed.</param>
protected void OnPropertyChanged<T>(Expression<Func<T>> expression)
{
    this.OnPropertyChanged(Reflect.GetProperty(expression).Name);
}

/// <summary>
/// Called when the property <paramref name="propertyName"/> has changed.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
    this.VerifyPropertyName(propertyName);
    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

Can you see in the protected void OnPropertyChanged<T>(Expression<Func<T>> expression) method that there is a Reflect.GetProperty() method call? Let us examine that in a bit more detail. This is as follows:

//--------------------------------------------------------------
// <copyright file="Reflect.cs" company="William E. Kempf">
//     Copyright (c) William E. Kempf.
// </copyright>
//---------------------------------------------------------------

namespace Onyx.Reflection
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>
    /// Performs static reflection.
    /// </summary>
    public static class Reflect
    {
        /// <summary>
        /// Gets a <see cref="MemberInfo"/> using static reflection.
        /// </summary>
        /// <param name="expression">An expression that uses member access.</param>
        /// <returns>
        /// A <see cref="MemberInfo"/> instance for the member accessed in the 
        /// <paramref name="expression"/>.
        /// </returns>
        public static MemberInfo GetMember(Expression<Action> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(
                    GetMember(() => expression).Name);
            }

            return GetMemberInfo(expression as LambdaExpression);
        }

        /// <summary>
        /// Gets a <see cref="MemberInfo"/> using static reflection.
        /// </summary>
        /// <typeparam name="T">The type of the member accessed in the 
        /// <paramref name="expression"/>.</typeparam>
        /// <param name="expression">An expression that uses member access.</param>
        /// <returns>
        /// A <see cref="MemberInfo"/> instance for the member accessed in the 
        /// <paramref name="expression"/>.
        /// </returns>
        public static MemberInfo GetMember<T>(Expression<Func<T>> expression)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(
                    GetMember(() => expression).Name);
            }

            return GetMemberInfo(expression as LambdaExpression);
        }

        /// <summary>
        /// Gets a <see cref="MethodInfo"/> using static reflection.
        /// </summary>
        /// <param name="expression">An expression that uses member access.</param>
        /// <returns>
        /// A <see cref="MethodInfo"/> instance for the member accessed in the 
        /// <paramref name="expression"/>.
        /// </returns>
        public static MethodInfo GetMethod(Expression<Action> expression)
        {
            MethodInfo method = GetMember(expression) as MethodInfo;
            if (method == null)
            {
                throw new ArgumentException(
                    "Not a method call expression", GetMember(() => expression).Name);
            }

            return method;
        }

        /// <summary>
        /// Gets a <see cref="PropertyInfo"/> using static reflection.
        /// </summary>
        /// <typeparam name="T">The type of the member accessed in the 
        /// <paramref name="expression"/>.</typeparam>
        /// <param name="expression">An expression that uses member access.</param>
        /// <returns>
        /// A <see cref="PropertyInfo"/> instance for the member accessed in the 
        /// <paramref name="expression"/>.
        /// </returns>
        public static PropertyInfo GetProperty<T>(Expression<Func<T>> expression)
        {
            PropertyInfo property = GetMember(expression) as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException(
                    "Not a property expression", GetMember(() => expression).Name);
            }

            return property;
        }

        /// <summary>
        /// Gets a <see cref="FieldInfo"/> using static reflection.
        /// </summary>
        /// <typeparam name="T">The type of the member accessed in the 
        /// <paramref name="expression"/>.</typeparam>
        /// <param name="expression">An expression that uses member access.</param>
        /// <returns>
        /// A <see cref="FieldInfo"/> instance for the member accessed in the 
        /// <paramref name="expression"/>.
        /// </returns>
        public static FieldInfo GetField<T>(Expression<Func<T>> expression)
        {
            FieldInfo field = GetMember(expression) as FieldInfo;
            if (field == null)
            {
                throw new ArgumentException(
                    "Not a field expression", GetMember(() => expression).Name);
            }

            return field;
        }

        internal static MemberInfo GetMemberInfo(LambdaExpression lambda)
        {
            if (lambda == null)
            {
                throw new ArgumentNullException(
                    GetMember(() => lambda).Name);
            }

            MemberExpression memberExpression = null;
            if (lambda.Body.NodeType == ExpressionType.Convert)
            {
                memberExpression = 
                  ((UnaryExpression)lambda.Body).Operand as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
            {
                memberExpression = lambda.Body as MemberExpression;
            }
            else if (lambda.Body.NodeType == ExpressionType.Call)
            {
                return ((MethodCallExpression)lambda.Body).Method;
            }

            if (memberExpression == null)
            {
                throw new ArgumentException(
                    "Not a member access", GetMember(() => lambda).Name);
            }

            return memberExpression.Member;
        }
    }
}

Bill talks a bit more about this over at his blog: Reflecting on Code: Static Reflection. It's all the rage.

Listening to Typical View Methods

One of the very nice things that Onyx provides is that it exposes some nice methods (which are raised as results of the associated View raising its own events) that one would expect to find on a typical Window, such as:

  • protected virtual void OnViewInitialized()
  • protected virtual void OnViewUnloaded()
  • protected virtual void OnViewLoaded()

Where Onyx does the following to create these methods:

protected ViewModel(View view)
{
    this.view = view;

    IElementLifetime lifetime = this.view.GetService<IElementLifetime>();
    if (lifetime != null)
    {

        lifetime.Initialized += (s, e) => this.OnViewInitialized();
        lifetime.Unloaded += (s, e) => this.OnViewUnloaded();
        lifetime.Loaded += (s, e) => this.OnViewLoaded();
    }
}

IElementLifetime is just an interface that Onyx uses to make a further abstraction for the View to be only these three methods.

So what does this mean to you if you are working in your ViewModel? Well, it means that we can simply override these methods and do things within our ViewModel when one of these events occurs within the View that the ViewModel is abstracting.

Here is an example that the attached demo code uses:

/// <summary>
/// Occurs when the associated View is loaded
/// </summary>
protected override void OnViewLoaded()
{
    base.OnViewLoaded();
    //run messagebox on Background give view some time for view to render properly
    base.View.ViewElement.Dispatcher.BeginInvoke((Action)delegate
    {
        var messageBoxService = this.View.GetService<IDisplayMessage>();
        messageBoxService.ShowMessage("Welcome to a small Onyx Demo.\r\n\r\n " +
            "Please pick the Disciples.xml file from the debug directory", 
            "Onyx demo", MessageBoxButton.OK,MessageBoxImage.Information);

    }, DispatcherPriority.Background);
}

In this section of code, I am using Onyx to locate a service called IDisplayMessage which can be used to display a MessageBox to the user via the associated View. Also of note here is that I am using the Dispatcher associated with the View, which again is exposed very easily in Onyx; you simply have to use base.View.ViewElement.Dispatcher to obtain the correct View's Dispatcher.

Future Onyx Work

I mentioned to Bill that I thought there was something missing which was the ability to open new actual WPF windows from a ViewModel and the whole issue of navigation. Bill is aware of this, but it is a complex subject; however, it is something that Bill mentioned would appear in Onyx within the future. So all I can say about that one is stay tuned and keep checking the Onyx CodePlex site.

That's it Folks

I would just like to close by stating that if you are doing some sort of WPF development, it will be well worth your while looking into Onyx, which as I stated is still a work in progress, but it is a good work in progress, that I have found to be very intuitive, and it actually caters for most nasties that would otherwise be a headache. Nice one Bill, it is really very, very cool. If I weren't so far into my current WPF project at work, I would be using this framework for sure, and certainly will for future projects.

Oh and another thing, of late, the WPF Disciples have all been having heavy discussions about a new CodePlex site for the community. This site is available at here.

Here is what this site will be aiming to cover:

M-V-VM Reference Application

Project Description

Community created reference application for M-V-VM frameworks to use for demonstration purposes, similar in concept to Pet Shop for web frameworks.

This project isn't going to give any solutions for following the Model-View-ViewModel pattern.

What it is going to do is provide a sample application that does "typical" things that cause problems when following an M-V-VM architecture. Then, given this reference application and supporting documentation, other implementations can be made using specific M-V-VM libraries.

Those other implementations won't be part of this project, though we'll provide links to them.

Instead, they'll be reference implementations provided by the specific M-V-VM library's author(s) or interested third parties. This makes it easier on the library developers, since they don't have to dream up their own reference implementation sample. It benefits the users, since they can compare various implementations using "competing" libraries, and they can learn "best practices" of how to use a given library. Again, if you're at all familiar, this is "Pet Shop" for WPF and Silverlight.

Check it out.

Useful Links

License

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

Share

About the Author

Sacha Barber
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)
 
- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence
 
Both of these at Sussex University UK.
 
Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions

 
General3D Effects Pinmembersufiyan_ansari128-Mar-10 2:58 
GeneralGreat Article PinmemberDan Wts18-Sep-09 10:46 
GeneralRe: Great Article PinmvpSacha Barber18-Sep-09 20:43 
GeneralNavigation for Onyx framework PinmemberSam Safonov6-Sep-09 1:41 
QuestionIs this correct? Pinmemberjh111122-Jul-09 8:04 
In "Setting Up The ViewModel" section,
 
AnswerRe: Is this correct? PinmvpSacha Barber22-Jul-09 22:46 
GeneralRun Spell Check before Final. PinmemberWade Beasley17-May-09 11:49 
General[My vote of 2] Hey images are not visible. Pinmemberww.mubshir.com15-May-09 4:13 
GeneralRe: [My vote of 2] Hey images are not visible. PinmvpSacha Barber15-May-09 4:35 
GeneralRe: [My vote of 2] Hey images are not visible. PinmemberTefik Becirovic16-May-09 6:25 
GeneralRe: [My vote of 2] Hey images are not visible. PinmvpSacha Barber17-May-09 3:37 
GeneralAgain PinmemberDr.Luiji13-May-09 22:15 
GeneralRe: Again PinmvpSacha Barber13-May-09 22:33 
GeneralCritical spelling failure PinmemberohnAskew6-May-09 9:37 
GeneralRe: Critical spelling failure PinmemberWilliam E. Kempf11-May-09 4:01 
GeneralRe: Critical spelling failure PinmvpSacha Barber13-May-09 0:30 
GeneralRe: Critical spelling failure PinmvpSacha Barber13-May-09 22:36 
QuestionCommand Registration - where does this happen? Pinmembernpcomplete130-Apr-09 6:40 
AnswerRe: Command Registration - where does this happen? PinmvpSacha Barber30-Apr-09 7:03 
QuestionRe: Command Registration - where does this happen? Pinmembernpcomplete130-Apr-09 7:53 
AnswerRe: Command Registration - where does this happen? PinmvpSacha Barber30-Apr-09 10:51 
GeneralRe: Command Registration - where does this happen? Pinmembernpcomplete11-May-09 4:22 
GeneralRe: Command Registration - where does this happen? PinmvpSacha Barber1-May-09 4:33 
Generalgood PinmemberMember 368237117-Apr-09 0:56 
GeneralRe: good PinmvpSacha Barber17-Apr-09 1:22 
GeneralGood Pingroupzhujinlong1984091316-Apr-09 21:34 
GeneralRe: Good PinmvpSacha Barber16-Apr-09 22:41 
GeneralMy Vote of 1 PinmemberRama Krishna Vavilala16-Apr-09 8:39 
GeneralRe: My Vote of 1 PinmvpSacha Barber16-Apr-09 11:11 
GeneralRe: My Vote of 1 Pinmemberfire_birdie22-Apr-09 6:26 
GeneralRe: My Vote of 1 PinmvpSacha Barber22-Apr-09 6:34 
GeneralEnjoyed Reading PinmvpAbhijit Jana16-Apr-09 3:47 
GeneralRe: Enjoyed Reading PinmvpSacha Barber16-Apr-09 4:26 
GeneralVery Good PinmemberPopaStefanMarian16-Apr-09 2:53 
GeneralRe: Very Good PinmvpSacha Barber16-Apr-09 3:18 
GeneralOnyx is interesting... Pinmemberparagme15-Apr-09 20:41 
GeneralRe: Onyx is interesting... PinmvpSacha Barber15-Apr-09 22:26 
GeneralMessage Automatically Removed PinmemberThisisTooMuch15-Apr-09 20:15 
GeneralRe: My vote of 1 Pinmemberparagme15-Apr-09 20:38 
GeneralRe: My vote of 1 Pinmemberrmarkram15-Apr-09 20:43 
GeneralRe: My vote of 1 PinmvpSacha Barber15-Apr-09 22:22 
GeneralRe: My vote of 1 PinmemberThisisTooMuch16-Apr-09 0:51 
GeneralRe: My vote of 1 PinmvpPete O'Hanlon16-Apr-09 1:38 
GeneralRe: My vote of 1 PinmemberWilliam E. Kempf16-Apr-09 1:49 
GeneralRe: My vote of 1 Pinmembersoup17-Apr-09 9:02 
GeneralRe: My vote of 1 PinmvpSacha Barber17-Apr-09 23:04 
GeneralRe: My vote of 1 Pinmembersoup20-Apr-09 15:21 
GeneralRe: My vote of 1 PinmvpSacha Barber20-Apr-09 22:37 
GeneralRe: My vote of 1 PinmemberLee Humphries14-May-09 19:31 
GeneralRe: My vote of 1 PinmvpSacha Barber14-May-09 22:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 21 May 2009
Article Copyright 2009 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid