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

Architecturing an Extensible and Flexible WPF Client Application using WPFWidgetizer Framework (Part 1)

Rate me:
Please Sign up or sign in to vote.
4.98/5 (19 votes)
27 Sep 2014CPOL36 min read 44.9K   483   38   7
Describe architecturing a flexible and extensible WPF application with the help of WPFWidgetizer framework

Important Note

Friends, I'll be happy to hear any feedback from you in the comments section. Thanks

Introduction

When a software team works on building an application, it is necessary to maintain a balance between enabling different people to work as independently of each other as possible and making the fruits of their labor work together seamlessly. Because of the need for this balance, almost every successful UI architecture splits the application into Widgets.

Widget is a primary UI unit that can be developed and tested independently of the rest of the Widgets and might have its own value for the users even outside of the whole application.

WPFWidgetizer framework based architecture presented in article will address the need for creating a flexible UI infrastructure consisting of Widgets seamlessly working together. The techniques presented here have been tested and found to be working in numerous successful projects.

Please, note that WPFWidgetizer framework is expected to continue undergo changes and further development.

Reading and working through this article will save millions of dollars for your project: by building a flexible infrastructure you will be able to concentrate on creating the individual widgets that you need for the business, instead of struggling to make them work together. Moreover, learning these techniques will allow you to avoid making wrong architectural decisions (architectural anti-patterns, which unfortunately plague many WPF projects).

We call this approach building "Extensible and Flexible" applications because it allows to create applications that consist of widgets that can be easily added, removed or re-configured. These widgets communicate with each other using Prism's EventAggregator messages.

In architecture (as opposed to implementation) the major unit of the application is a widget, not individual controls, so we are going to concentrate on describing how to build the application at the widget level. Of course, we will also be using some simple controls as finer building blocks, but the focus will be on the widgets.

We almost avoid using any third party components within our application, with the exception of Microsoft's Prism and Microsoft's Expression Blend SDK.

We use Prism as an Inversion of Control (IoC) container and also for its event aggregator messaging functionality.

Expression Blend SDK is being used to provide the plumbing between XAML and C# code. Unlike Expression Blend, Expression Blend SDK is free to use and redistribute.

All the required dll files for Prism and Exression Blend SDK are provided with our samples, so you do not have to download anything extra.

Most of the techniques described in this article can be applied to building any UI client application, not necessarily a WPF or even .NET one. All of our samples, however, are built using WPF.

We do not discuss here implementing the database and service layers for the applicaion. We do, however, show how to implement a mock service layer that would return mock data to the client. We also present a mechanism for swapping this mock service layer with a real one with almost no change for the rest of the application.

This article is not for WPF beginners - I assume some knowledge of WPF, including MVVM pattern, dependency, attached and notifiable properties, bindings, routed events, XAML.

Article Overview

Contrary to my original intention, the material I want to cover proved to be too large for one article - it will have to be divided into two installments. Here are the topics covered in this installment:

Here are the major topics we are going to cover in this article:

  1. Refresher on using Prism as IoC Container and Event Aggregation.
  2. Widget - Widgets Assembly Pattern - this is the core pattern of the article.
  3. Discussion of the Code Structure.
  4. Exploration of the View Model and UI code as well as data fetching functionality via the Single Widget Sample.
  5. Exploration of Widget UI action events, inter-Widget messaging and Widget Assembly functionality via the Simple Widget Assembly Sample.

 

And here are the major topics that will be covered in the sequel:

  1. Widgets that contain Widget Assembly with sub-widgets within them.
  2. Scope based hierarchical inter-Widget communications.
  3. Navigation between different Widget Assemblies.
  4. Element Factory Pattern.
  5. View Model Hierarchy Pattern.

 

Refresher on Inversion of Control (IoC) and Event Aggregation using MS Prism Framework

I recommend that even those who know the IoC and Event Aggregation well, should go over this refresher, since it describes how we use these concepts further in the article and gives some ideas about the code structure.

Inversion of Control (IoC)

What IoC is Used For

IoC allows to swap parts of the application that implement certain interfaces with completely different implementations of the same iterfaces with little or no code change. Most of the time IoC is used for providing temporary or test plugins for some (usually non-visual) parts of the application. For example, in many projects I was using a mock data plugin in order to build an application before the backend/services are ready for it. Even if the backend is ready for usage, you might still want to simpulate your own mock data to test that the application behaves well under some rare conditions or simply to make the data return faster.

There are many various types of IoC containers including MEF and Unity. Throughout this article we are going to be using MEF container that is part of Microsoft's Prism software.

IoC Overuse Anti-Pattern

Many application architects might disagree with me, but I am convinced that in many projects the IoC is greatly overused. Unfortunately, MEF and other IoC containers facilitate chain propagation of plugins, meaning - if a component is implemented as a MEF plugin, it is easier to implement the classes that contains this component as a MEF plugin also.

Overusing of plugin architecture leads to confusing code, unclear exceptions and difficulty in debugging while it does not make the application any more flexible.

Consider yourself cautioned, use plugins only for those parts whose implementation you really might want to swap for a different one at some point during the project!

IoC Prism Sample

We are using Prism 5.0 for .NET 4.5 freely available at Microsoft Prism Download.

You do not have to download and install the packages from the above URL, since we provide all necessary dll files with our samples.

The sample is located under IoCPluginSample.sln solution within Samples/IoCPluginSample folder.

Before opening the project, you should unblock the Prism dll files (they are likely to be blocked since you downloaded them from the internet). To unblock them, go to ExternalPackages/Prism folder which contains them, right click on each file, choose Properties option and click Unblock button.

The purpose of this sample is to show how easy it is to swap between different implementation of the same interface by using Prism with MEF.

Interface INumberChurner is defined under GenericInfrastructure project:

C#
[InheritedExport(typeof(INumberChurner))]
public interface INumberChurner
{
    int GetInt();
}

It has only one method GetInt() that returns an integer. Two different projects SampleMockup1 and SampleMockup2 are located under TestAndMockupUtils folder. They provide two different implementations of INumberChurner - classes Number1Churner and Number2Churner.

GetInt() method of Number1Churner always returns 1 while GetInt() method of Number2Churner always returns 2.

InheritedExport(typeof(INumberChurner)) MEF attributed above INumberChurner declaration, ensures that classes that implement INumberChurner are MEF exports with the same MEF id provided by typeof(INumberChurner) or, in other words that can be swapped by MEF.

MEF allows you to get implementation by MEF id from a MEF container. Because of this, it is important to have an easy way of getting a reference to MEF container all over the application. To provide such reference, we created a static class TheAppIoCContainer under GenericInfrastructure project. This class contains TheCompositionContainer property that refers to the MEF container throughout the application. This property is set to be a reference to the MEF container by AppBootstrapperBase class:

C#
public abstract class AppBootstrapperBase : MefBootstrapper
{
    // called when the application is initialized
    protected override System.ComponentModel.Composition.Hosting.CompositionContainer CreateContainer()
    {
        CompositionContainer theContainer = base.CreateContainer();

        // set the static reference to the MEF container
        TheAppIoCContainer.TheCompositionContainer = theContainer;

        return theContainer;
    }
}

Now, let us switch our attention to IoCPluginSample project (the main project of the application).

TheBootstrapper class under IoCPluginSample project inherits from GenericInfrastructure.AppBootstrapperBase class:

C#
public class TheBootstrapper : AppBootstrapperBase
{
    protected override void InitializeShell()
    {
        base.InitializeShell();

        Application.Current.MainWindow = this.Shell as Window;

        Application.Current.MainWindow.Show();
    }

    protected override DependencyObject CreateShell()
    {
        return Container.GetExportedValue<mainwindow>();
    }

    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));

        // you can swap between SampleMockup1 and SampleMockup2 implementations
        // of common interaces by changing the commented out line below
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number1Churner).Assembly));
        //AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number2Churner).Assembly));
    }
}  
</mainwindow>

Method ConfigureAggregateCatalog is the one that we use for swapping implementations between that of SampleMockups1 and of SampleMockup2 projects - in order to choose one implementation over the other, all you need to do is to leave the corresponding AggregateCatalog.Catalogs.Add(...) line uncommented and comment out the other line.

Class MainWindow defines a integer property TheNumber which is set by the corresponding NumberChurner:

C#
[Export]
public partial class MainWindow : Window
{
    public MainWindow()
    {
        // get the number churner provide by MEF
        INumberChurner numberChurner =
            TheAppIoCContainer.TheCompositionContainer.GetExportedValue<inumberchurner>();

        // set TheNumber property
        TheNumber = numberChurner.GetInt();


        InitializeComponent();
    }

    public int TheNumber
    {
        get;
        private set;
    }
} 
</inumberchurner>

MainWindow.xaml displays TheNumber property of the MainWindow class:

XML
<Grid>
    <TextBlock Text="{Binding Path=TheNumber, 
                              RelativeSource={RelativeSource AncestorType=Window}}" 
               HorizontalAlignment="Center"
               VerticalAlignment="Center"
               FontSize="80"/>
</Grid>  

When we run the application, it will show number 1 in the middle of the white window.

Image 1

If, however, we change the ConfigureAggregateCatalog() method of TheBootstrapper class to have second AggregateCatalog.Catalogs.Add(...) line uncommented:

C#
//AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number1Churner).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number2Churner).Assembly));  

The application will show number 2.

Note, that if we have more methods within our iterface, or different interfaces, and various assemblies providing different implementation for those interfaces, we would still be able to change between these different implementations by changing the comments on one line within TheBootstrapper.ConfigureAggregateCatalog() method.

Before we continue, I suggest a small improvement to the code above. TheBootstrapper class will have to be part of the start up project of every sample (we won't be able to reuse it). It makes sense, to factor out most of its functionality into a superclass. In particular, we can factor out functions InitializeShell() and CreateShell(). We already have our own superclass AppBootstrapperBase defined under GenericInfrastructure project. We do not want to use this class, for defining the functions InitializeShell() and CreateShell(), however, because it will require adding a dependency on some WPF specific dlls for the GenericInfrastructure project and we want to be able to use its functionality in purely non-visual projects. Because of that, we add a project IoCPluginUtils and define there a class AppBootstrapper derived from AppBootstrapperBase. AppBootstrapper will provide all the plumbing functionality, so that the individual sample bootstrappers will only have to define ConfigureAggregateCatalog() method:

C#
public class AppBootstrapper<T> : AppBootstrapperBase where T : Window
{
    protected override void InitializeShell()
    {
        base.InitializeShell();
        Application.Current.MainWindow = this.Shell as Window;

        Application.Current.MainWindow.Show();
    }

    protected override DependencyObject CreateShell()
    {
        return Container.GetExportedValue<T>();
    }

    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();

        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(T).Assembly));
    }
}  

And TheBootstrapper class of the main project becomes mere:

C#
public class TheBootstrapper : AppBootstrapper<MainWindow>
{
    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();

        // you can swap between SampleMockup1 and SampleMockup2 implementations
        // of common interaces by changing the commented out line below
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number1Churner).Assembly));
        //AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Number2Churner).Assembly));
    }
}  

This is all we need from the IoC capabilities of Prism.

Event Aggregation

What Event Aggregation is Used for

Event aggregation allows separate parts of the application communicate with each other without having any knowledge of each other - thus enabling better separation of concerns. One of the parts publishes a message via an EventAggregator while the other part(s) can register a handler for that type of the message, so that when the message arrives, the handler fires.

Most WPF frameworks, provide their own Event Aggregation functionalty. Here we shall be using Prism event aggregator.

Event Aggregation Overuse Anti-Pattern

Just like the IoC, the Event Aggregation should be used with caution. When people adopt event aggregation they tend to overuse it for almost every communication within the application. This leads to compicated spaghetti code difficult to extend, debug and understand.

I suggest using Event Aggregation only for communications between larger entities within the application, in particular for communications between the Widgets, while the communication within each Widget should be handled differently. We'll speak much more about it below.

Event Aggregation Sample

The Prism Event Aggregation sample is located under EventAggregatorSample.sln solution within EventAggregatorSample folder. The main project bears the same name EventAggregatorSample.

Even though, I recommended above to use Event Aggregation only at the widget level or higher, here, for simplicity sake, I'll demonstrate using the event aggregation for communications between different controls defined within the same window; inter-Widget communication will be explained further down in this article.

Try running the project, you will the following:

Image 2

If you press "Publish Time Stamp" button, nothing is going to happen because the subscription to the event has not taken place yet.

If you check "Subscribe/Unsubscribe" checkbox, however, and try pushing the button again, you will see time stamps published on the right hand side of the window:

Image 3

Now, let us describe the code.

Under project GenericInfrastructure I created a class EventAggregatorSingleton. It is a wrapper around Prism's Event Aggregator functionality. It is static, so that it simplifies access to the event aggregator (I've never heard of using more than one Event Aggregator within an application, so we are not losing any genericity). This class contains several very simple methods for publishing messages, subscribing to messages and unsubscribing from messages:

C#
public static class EventAggregatorSingleton
{
    // a global reference to the event aggregator
    static IEventAggregator _eventAggregator = null;
    private static IEventAggregator TheEventAggregator
    {
        get
        {
            if (_eventAggregator == null)
            {
                if (TheAppIoCContainer.TheCompositionContainer == null)
                    return null;

                _eventAggregator =
                    TheAppIoCContainer.TheCompositionContainer.GetExportedValue<IEventAggregator>();
            }
            return _eventAggregator;
        }
    }

    // utility method for getting the prism aggregation event. 
    private static PubSubEvent<T> GetPrismAggregationEvent<T>()
    {
        PubSubEvent<T> prismAggregationEvent =
            TheEventAggregator.GetEvent<PubSubEvent<T>>();

        return prismAggregationEvent;
    }

    // publishing method
    public static void Publish<T>(T eventAggregatorMessage)
    {
        GetPrismAggregationEvent<T>().Publish(eventAggregatorMessage);
    }

    // subscribing method
    public static PrismUnsubscriber Subscribe<T>
    (
        Action<T> action, 
        Predicate<T> filter = null,
        ThreadOption threadOption = ThreadOption.PublisherThread,
        bool keepSubscriberReferenceAlive = true
    )
    {
        PubSubEvent<T> prismAggregationEvent = GetPrismAggregationEvent<T>();

        SubscriptionToken subscriptionToken =
            prismAggregationEvent.Subscribe
            (
                action,
                threadOption,
                keepSubscriberReferenceAlive,
                filter
            );

        PrismUnsubscriber result =
            new PrismUnsubscriber(prismAggregationEvent, subscriptionToken);

        return result;
    }

    // unsubscribe by token
    public static void Unsubscribe<T>(this SubscriptionToken token)
    {
        PubSubEvent<T> prismAggregationEvent = GetPrismAggregationEvent<T>();

        prismAggregationEvent.Unsubscribe(token);
    }

    // unsubscribe by callback
    public static void Unsubscribe<T>(Action<T> action)
    {
        PubSubEvent<T> prismAggregationEvent = GetPrismAggregationEvent<T>();

        prismAggregationEvent.Unsubscribe(action);
    }
}

You can see, that publishing/subscribing to Prism events is done by the message's C# type. If you subscribe to a certain message type, you'll be receiving all the published messages of that C# type. There is, however, a filtering parameter to the Subscribe(...) function that allows to refine the subscription only to messages that satisfy the filtering condition.

Most of the sample specific functionality is located under the main project of the application: EventAggregatorSample project within MainWindow.xaml and MainWindow.xaml.cs files. This project is referencing two dll files from MS Expression Blend SDK - Microsoft.Expression.Interactions.dll and System.Windows.Interactivity.dll. They are provided under ExternalPackages/ExpressionBlendSDK folder. You need to unblock these files in the same way as you have unblocked the prism dlls. BTW, these files just like the whole MS Expression Blend SDK (unlike Expression Blend itself) are free to use and re-distribute.

We use functionality from the MS Expression Blend SDKs in order to call C# functions from within XAML. To learn more about MS Expression Blend SDK, you can read e.g. parts of MVVM Pattern Made Simple.

Let us first take a look at MainWindow.xaml.cs file. It defines TheDateTimeCollection as an ObservableCollection<datetime></datetime>. This collection is being populated by the subscription callback provided by OnTypeStampMessageArrived(DateTime timeStamp) method.

PublishTimeStamp() method publishes the current time stamp via the event aggregator.

IsSubscribed boolean property allows to toggle between subscribed and unsubscribed states. It invokes subscription when the propery changes to true and unsubscription when it changes to false.

Here is the code for MainWindow.xaml.cs file:

C#
[Export]
public partial class MainWindow : Window, INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;
    #endregion

    protected void OnPropChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public MainWindow()
    {
        TheDateTimeCollection = new ObservableCollection<DateTime>();

        InitializeComponent();
    }

    // accumulates the timestamps
    public ObservableCollection<DateTime> TheDateTimeCollection { get; private set; }

    // publishes the current timestamp view Prism event aggregator
    public void PublishTimeStamp()
    {
        DateTime timestampToPublish = DateTime.Now;
        EventAggregatorSingleton.Publish<DateTime>(timestampToPublish);
    }


    PrismUnsubscriber _subscriptionToken = null;

    bool _isSubscribed = false;
    // this property toggles IsSubscribed state of the application
    // when IsSubscribed=false, the published timestamps are not added
    // while when it is true, they are added to TheDateTimeCollection 
    // collection
    public bool IsSubscribed
    {
        get
        {
            return _isSubscribed;
        }

        set
        {
            if (_isSubscribed == value)
                return;

            _isSubscribed = value;

            if (_isSubscribed)
            {
                // subscribe to DateTime timestamp messages
                _subscriptionToken = EventAggregatorSingleton.Subscribe<DateTime>(OnTimeStampMessageArrived);
            }
            else
            {
                // unsubscribe from the DateTime timestamp messages
                if (_subscriptionToken != null)
                {
                    _subscriptionToken.Unsubscribe();
                    _subscriptionToken = null;
                }
            }

            // notify the UI that IsSubscribed property has changed
            OnPropChanged("IsSubscribed");
        }
    }

    // this is the timestamp subscription callback, 
    // called when each new timestamp arrives in case 
    // the subscription is on.
    private void OnTimeStampMessageArrived(DateTime timeStamp)
    {
        TheDateTimeCollection.Add(timeStamp);
    }
}

MainWindow.xaml file creates UI elements for using the functionality defined in MainWindow.xaml.cs file:

XML
<Window x:Class="EventAggregatorSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- button that triggers publishing the timestamp -->
            <Button Width="120"
                    Height="25"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Content="Publish Time Stamp">
                <i:Interaction.Triggers>
                    <!-- Call PublishTimeStamp() method of the MainWindow class when the button is clicked -->
                    <i:EventTrigger EventName="Click">
                        <ei:CallMethodAction MethodName="PublishTimeStamp"
                                             TargetObject="{Binding RelativeSource={RelativeSource AncestorType=Window}}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>

            <StackPanel Grid.Column="1"
                        Margin="10">
                <TextBlock Text="Display Published Timestamps" />
                
                <!-- ListView that receives the published timestamps-->
                <ListView Margin="0,20,0,20"
                          ItemsSource="{Binding Path=TheDateTimeCollection,
                                                RelativeSource={RelativeSource AncestorType=Window}}"/>
            </StackPanel>
        </Grid>

        <!-- CheckBox that controls whether the subscription 
             to TimeStamp events is on or off-->
        <CheckBox Grid.Row="1"
                  Content="Subscribe/Unsubscribe" 
                  Margin="10,5,10,30"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"
                  IsChecked="{Binding Path=IsSubscribed, RelativeSource={RelativeSource AncestorType=Window}}"/>
    </Grid>
</Window>  

Widgets and Widgets Assembly Pattern

Widgets

Each Widget has its own View Model and its own wiring for getting the data from the backend/service layers. The widgets should also provide a mechanism for communicating with other widgets, preferably without knowing the other widget's types - so the Event Aggregation concept is ideal for communicating between different widgets. Widgets in WPFWidgetizer are descendants of WPF ContentControl class. Each widget usually contains some finer level WPF Control(s).

Widgets Assembly Pattern

In order for the several widgets to display and work together within an application, they should be assembled together into some larger entities. Often, such entities are called 'Views' (not to be confused with the Views of MVVM pattern). In this article, however, precisely in order to avoid confusion with the Views of the MVVM pattern we are going to call them "Widget Assemblies".

The purpose of a Widget Assembly is to define the mutual location and communications between the Widgets within it.

A lot of times Widget Assemblies are implemented in C#/XAML. They can be defined as usual WPF controls containing the widgets. Widget Assembly implementation can even include a View Model that would contain the View Models of the individual widgets and coordinate their behaviors. This approach, however, has a number of shortcomings:

  1. It increases complexity of the application. Each Widget Assembly will result in XAML/C# View files and also in a View Model file.
  2. It reduces the flexibility/configurability of the application - since C# functionality is usually difficult to imitate by configuration files.
  3. It reduces the uniformity of the application - C# is a very powerful language - using it for communications between the widgets might result in a wide range of different solutions by different developers on the project.

 

In order to improve the architecture and the speed of the development, the Widget Assembly functionality should be made as generic as possible. The solution proposed in this article has only one class for Widget Assembly, but multiple styles that specify the mutual location of the widgets. Using 3rd party widget docking functionality e.g. from Teleric or DevExpress, one can futher improve the genericity of the solution by having the Widget Assemblies defined by configuration files that can be modified at run time. This approach (even though it is beyond the scope of this article) would allow creating dynamic Widget Assemblies that can be put together by the users. This will result in unparallel flexibility enabling creating the Views almost on the fly by combining various Widgets together.

Code Structure

Here we describe the location of the projects for WPFWidgetizer framework and the samples and the relationships between them.

All the samples below show how to create the functionality for displaying various information about an imaginary book store, so the code can be sub-divided into generic and book store specific functionality.

The bird's eye view of the file structure of the WPFWidgetizer code is given by the following image:

Image 4

"ExternalPackages" folder contains the dll files for the 3rd party components that we use: Prism and Expression Blend SDK.

The "GenericFramework" folder, contains generic functionality that can be used not only for building a book store application, but any application. This functionality can be distributed as dll files corresponding to the individual projects within "GenericFramework" folder.

Here is a brief description of all the "GenericFramework" projects (ordered from more generic to more specific):

  1. GenericInfrastructure is a project containing very generic non-visual utilities that can be used in any project and are not aware of Widget View Models or Widgets. It contains some basic utility wrappers around Prism's Event Aggregation and IoC functionality. It can be extened, e.g. to contain some generic extension methods for string manipulations etc.
  2. UIControls project contains generic UI Controls and UI styles that are not aware of widgets or Widget View Models.
  3. IoCPluginUtils contain only a generic bootstrapper class AppBootstrapper that has been described above.
  4. RecordVMs project contains a generic non-visual code and base classes corresponding to the individual rows coming from the database/service layer (we assume that the data coming from the database is a collection of such rows).
  5. WidgetVM project provides the generic base and utility non-visual classes for the Widgets' View Models.
  6. WidgetsAndAssemblies project provides base and utility UI classes and styles for the widgets themselves.

 

"BookStoreSpecificFramework" folder contains projects specific to our book store related screen development:

 

  1. BookStoreInfrastructure contains book store specific enumerations and non-visual helper classes.
  2. BookStoreRecordVMs contains View Model classes for representing row data coming from the database or the service layer.
  3. BookStoreWidgetVMs contain book store specific Widgets' View Models.
  4. BookStoreWidgets contain UI classes and Styles for the book store related widgets.

 

"TestAndMockupUtils" folder contains projects facilitating tests and mockups. E.g. it has project MockupServiceLayer that mocks up the data coming from database/service layer.

"Samples" folder contains solutions and main projects for various samples (two of them IoCPluginSample and EventAggregatorSample were described above).

Here is a diagram of project dependencies (the arrows are pointing to the dependency target project):

Image 5

On the picture above and also in the discussions below, for the sake of brevity, I omit the namespace prefix to the project name e.g. the full project name is WPFWidgetizer.BookStoreSpecificFramework.BookStoreInfrastructure, while on the picture it is shown simply as BookStoreInfrastructure.

Note that projects under BookStore Specific Framework (on the right) depend on the corresponding projects from within Generic Framework (on the left).

Every project has a dependency on GenericInfrastructure project and MainApplication depends on all the rest of the projects (even though we do not show all the arrows coming out of GenericInfrastructure and ending at MainApplication - otherwise the picture will become a mess).

There is no project called MainApplication - the MainApplication box on the image corresponds to any of our sample application projects.

MockupServiceLayer contains an implementation of IBookStoreDataServiceAccessor (this interface is defined in BookStoreInfrastructure project). TheBootstrapper class of the main project is choosing the implementation of IBookStoreDataServiceAccessor to be MockupDataAccessor from the MockupServiceLayer project.

MockupServiceLayer contains functionality that returns a collection of items from BookStoreRecordVMs project. This is why we have to split Record VM from Widget VM projects - the (mock or real) data service layer depends on Record VM project (it should know how to populate and return collections containing Record VM objects), while Widget VM project should depend on the data service layer (it should know how to call its API).

Patterns and Samples

Sample Code Location

All the samples are located under "WPFWidgetizer\Samples" folder.

Single Widget Sample

Important Note

We shall use this sample to give a detailed oveview of most of the WPFWidgetizer framework; so, you should read this sub-section carefully and perhaps even play with the corresponding code within the debugger.

Running the Sample

Project SingleWidgetSample shows how to create a widget and load the data into it.

When you start the application, you see a blank window with the "Load Data from Server" button at its bottom:

Image 6

After the button is pressed, for several seconds you'll see the following screen

Image 7

Finally, after all the data is loaded, we see a data grid within the window:

Image 8

Now, let us take a look at the code.

Main Project's Code

Here is the code from MainWindow.xaml file that defines the widget and the load button:

XML
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid>
        <bookStoreWidgets:BookOrdersWidget x:Name="TheBookOrdersWidget" />
    </Grid>

    <Button x:Name="LoadDataButton"
            Content="Load Data from Server"
            Width="200"
            Height="25"
            Margin="0,10" 
            Grid.Row="1"/>
</Grid>  

All the styles are connected via ResourceDictionary.MergeDictionary statements at the top of the file:

XML
<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="/WPFWidgetizer.GenericFramework.UIControls;Component/Themes/DataGridControlStyles.xaml" />
            <ResourceDictionary Source="/WPFWidgetizer.BookStoreSpecificFramework.BookStoreWidgets;Component/Themes/BookWidgetStylesReferences.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>  

The code-behind file MainWindow.xaml.cs has the following handler for the Click event of the LoadDataButton:

C#
async void LoadDataButton_Click(object sender, RoutedEventArgs e)
{
    await TheBookOrdersWidget.LoadData();
}  

View Model Class Hierarchy

Before venturing further, let us take a peek at the View Model class hierarchy:

Image 9

Some of the classes in the hierarchy have generic type arguments which I skipped on the diagram in order to make it a little more readable.

The classes that we deal with in the samples are located at the bottom of the hierarchy, but we'll start describing the View Model classes from the top.

 

  • WidgetCommunicationsBase is a class providing some wirings for inter-widget communications via Prism's EventAggregator. We'll discuss it in detail later.
  • WidgetBaseVM provides functionaltiy for defining various widget states (Loading, Loaded or Exception). Its method LoadData() controls is in control of the changing Widget state. It also calls method LoadDataImpl() that is responsible for fetching data from the real or mock data service. LoadDataImpl() is defined as an abstract method in WidgetBaseVM and should be overridded in one of its subclasses.
  • WidgetWithItemsCollectionVM<RecordType> is a View Model representing a Widget containing a collection of records. It contains ItemsSource property to hold the collection. LoadDataImpl() function defined in this class calls an abstract function GetData to fetch the records from the service layer and populate ItemsSource property.
  • WidgetWithHighlightableItemsCollectionVM<RecordType> in addition to the functionality provided by WidgetWithItemsCollectionVM<RecordType> class, also allows marking some rows as "highlighted", as will be detailed later.
  • BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType> provides an implementation of GetData() method (declared abstract in the superclass). It also defines book store specific TheServiceRequestType and TheInputParams properties required for getting data from the server.
  • BookOrdersWidgetVM and BookReviewsWidgetVM are realizations of BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType> specifying the concrete RecordType and setting some of the properties within their contstructor.

 

Overview or the View Model Functionality and Data Fetching Functionality

Now take a look at the widget's LoadData() method's implementation. This should give you a pretty good idea of how the data is loaded into a single widget.

LoadData() method is implemented by WidtgetWithContext<WidgetVMType> class located within WPFWidgetizer.GenericFramework.WidgetsAndAssemblies project and namespace. All this method does, is - it calls the corresponding LoadData() method on the View Model of the widget:

C#
public override async Task LoadData()
{
    await TheVM.LoadData();
}  

The View Model's LoadData() method is implemented by WidgetVM class of WPFWidgetizer.GenericFramework.WidgetVMs project (and namespace):

C#
// loads data, e.g. from the server while controlling the
// state of the widget
public async Task LoadData()
{
    // set the widget state to be 'Loading' 
    // while the widget's data is being loaded
    this.TheLoadingState = WidgetLoadingState.Loading;

    try
    {
        // load the data
        await LoadDataImpl();

        // if loading the data did not throw an exception
        // set the state of the widget to 'Loaded'
        this.TheLoadingState = WidgetLoadingState.Loaded;
    }
    catch(Exception e)
    {
        // if an exception was thrown while the data is loaded
        // set the widget's state to 'Exception'
        // and set the LastDataLoadingException to hold
        // the corresponding exception.
        this.TheLoadingState = WidgetLoadingState.Exception;
        this.LastDataLoadingException = e;
    }
}  

As explained in the comments, LoadData() method controls the Widget's state while the data loading takes place. Widget State is defined by TheLoadingState property of the Widget's View Model. It is an Enumeration of WidgetLoadingState type defined under WPFWidgetizer.GenericFramework.WidgetVMs project (namespace).

In order to actually get data (from a real or mock server/service), LoadData() calls abstract method LoadDataImpl(). This method is overridden in the one of the subclasses of WidgetBadgeVM class.

For this sample, we are using BookOrdersWidget. It is derived from WidgetWithHighlightableDataGrid<BookOrdersWidgetVM, BookOrderRecordVM> class. The Generic type arguments of the latter class specify the View Model of the whole widget and the View Model of each row within the Widget's grid correspondingly:

public class BookOrdersWidget :
    WidgetWithHighlightableDataGrid<BookOrdersWidgetVM, BookOrderRecordVM>
{
} 

The Widget's View Model type is thus BookOrdersWidgetVM, which is derived from BookStoreWidgetWithHighlightableItemsCollectionVM<BookOrderRecordVM> class:

C#
public class BookOrdersWidgetVM : 
    BookStoreWidgetWithHighlightableItemsCollectionVM<BookOrderRecordVM<
{
    public BookOrdersWidgetVM()
    {
        this.TheServiceRequestType = BookStoreDataServiceRequestType.BookOrder;
        PropertyNameToHightlightOn = "BookCode";
    }
}  

BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType> is in turn derived from WidgetWithHighlightableItemsCollectionVM<RecordType> class.

WidgetWithHighlightableItemsCollectionVM<RecordType> provides some plumbing for row highlighting which will be discussed later. At this point, however we are interested in it's base class (superclass) WidgetWithItemsCollection<RecordType> which defines the ItemsSource property for holding the collection of records for the Widget. It also provides an implementation for LoadDataImpl() method that calls another abstract function GetItemsSource() for retrieving the widget's records from real or mockup data service layer:

C#
public abstract class WidgetWithItemsCollectionVM<RecordType> :
    WidgetBaseVM
    where RecordType : IRecordVM
{
    /// <summary>
    /// holds the Widget's records;
    /// </summary>
    #region ItemsSource Property
    private IEnumerable<RecordType> _itemsSourcee;
    public IEnumerable<RecordType> ItemsSource
    {
        get
        {
            return this._itemsSourcee;
        }
        set
        {
            if (this._itemsSourcee == value)
            {
                return;
            }

            this._itemsSourcee = value;
            this.OnPropertyChanged("ItemsSource");
        }
    }
    #endregion ItemsSource Property

    // should be overridden to return a collection of 
    // data records from the data service.
    protected abstract Task<IEnumerable<RecordType>> GetItemsSource();

    public async override Task LoadDataImpl()
    {
        // set the ItemsSource property to contain the Widget's records
        // obtained by GetItemsSource() function from the 
        // data service layer
        ItemsSource = await GetItemsSource();
    }
}  

The implementation of GetItemsSource() method is located in a View Model class we've discussed before - BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType>. This is a base class for all the BookStore widgets that contain a data grid. Here is the code:

C#
protected async override Task<IEnumerable<RecordType>> GetItemsSource()
{
    IEnumerable<RecordType> result =
        await BookStoreDataServiceAccessorSingleton.
                 TheDataServiceAccessor.
                 LoadServiceData<RecordType>
    (
        this.TheServiceRequestType,
        this.TheInputParams
    );

    return result;
}  

We contact the data accessor singleton calling its LoadServiceData(...) method and passing the Widget's TheServiceRequestType and TheInputParams arguments.

TheServiceRequestType usually corresponds to the name of the service we want to call (e.g. it can be a name of a DB stored procedure), while TheInputParams property is just a container of the name-value pairs that specify what parameters we want to pass to that service.

Both these parameters are defined within the same class BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType>. Note, however, that we will probably need these parameter for any Book Store related request not only for those with "highlightable item collections". We can factor these two properties out in a separate super-class, but then because of the lack of multiple implementation inheritance in C#, we'll have trouble attaching them to our class, since it is already derived from WidgetWithHighlightableItemsCollectionVM<RecordType>. Because of this problem, we are forced to re-implement these properties in all the Book Store View Models that do not derive from our class. We did, the next best thing, however, and factored these two properties out into IBookStoreWidget interface so that we could use various Book Store View Model classes in a unified way. Our BookStoreWidgetWithHighlightableItemsCollectionVM<RecordType> class implements this interface and provides an implementation for these two properties.

Let us go back to the implementation of our GetItemsSource() function:

C#
protected async override Task<IEnumerable<RecordType>> GetItemsSource()
{
    IEnumerable<RecordType> result =
        await BookStoreDataServiceAccessorSingleton.
                TheDataServiceAccessor.
                LoadServiceData<RecordType>
                (
                    this.TheServiceRequestType,
                    this.TheInputParams
                );

    return result;
}  

It uses LoadServiceData<RecordType>(...) method declared within IGenericDataServiceAccessor<DataServiceRequestTypeEnum> interface:

C#
public interface IGenericDataServiceAccessor<DataServiceRequestTypeEnum>
    where DataServiceRequestTypeEnum : struct, IConvertible
{
    Task<IEnumerable<T>> LoadServiceData<T>
    (
        DataServiceRequestTypeEnum serviceRequestType,
        ServiceRequestInputParams inputParams
    );
}  

Book Store specific data service accessor IBookStoreDataServiceAccessor inherits from IGenericDataServiceAccessor<DataServiceRequestTypeEnum> interface and is MEFable:

C#
[InheritedExport(typeof(IBookStoreDataServiceAccessor))]
public interface IBookStoreDataServiceAccessor : IGenericDataServiceAccessor<BookStoreDataServiceRequestType>
{

}  

GenericDataServiceAccessorSingleton<DataServiceAccessor, DataServiceRequestTypeEnum> class pulles and implementation of DataServiceAccessor type out of the MEF container:

C#
public static DataServiceAccessor TheDataServiceAccessor
{
    get
    {
        if (TheAppIoCContainer.TheCompositionContainer == null)
            return null;

        DataServiceAccessor result =
            TheAppIoCContainer.
                TheCompositionContainer.
                GetExportedValue<DataServiceAccessor>();

        return result;
    }
}  

BookStoreDataServiceAccessorSingleton class calls the functionality of GenericDataServiceAccessorSingleton<DataServiceAccessor, DataServiceRequestTypeEnum> to return the current implementation of IBookStoreDataServiceAccessor interface.

TheBootstrapper class of the main project takes care of bootstrapping the correct implementation of IBookStoreDataServiceAccessor interface (in our case it is MockupDataAccessor class of WPFWidgetizer.TestAndMockupUtils.MockupServiceLayer project (namespace):

C#
public class TheBootstrapper : AppBootstrapper<mainwindow>
{
    protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();

        // add MockupDataAccessor implementation
        // If there is a real servce layer accessor implemented
        // it can be swapped with the mockup one simply by 
        // replacing this one line with a reference to the real
        // data service.
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(MockupDataAccessor).Assembly));
    }
}  
</mainwindow>

Here is the implementation of MockupDataAccessor class that's actually being called:

C#
public class MockupDataAccessor : IBookStoreDataServiceAccessor
{
    public async Task<IEnumerable<T>> LoadServiceData<T>
    (
        BookStoreDataServiceRequestType serviceRequestType,
        ServiceRequestInputParams inputParams
    )
    {
        //throw new Exception("This is an exception for testing the widget error messages");

        switch(serviceRequestType)
        {
            case BookStoreDataServiceRequestType.BookOrder:
                // delay to give impression of a roundtrip to the server
                await Task.Delay(2000);
                return new MockBookOrderRecords() as IEnumerable<T>;
            case BookStoreDataServiceRequestType.BookReview:
                // delay to give impression of a roundtrip to the server
                await Task.Delay(3000);
                return new MockBookReviewRecords() as IEnumerable<T>;
            case BookStoreDataServiceRequestType.Book:
                return new MockBooks() as IEnumerable<T>;
            default:
                return null;
        }
    }
}  

When we call it from BookOrdersWidgetVM we are passing ServiceRequestType argument as BookStoreDataServiceRequestType.BookOrder; correspondingly, it returns MockBookOrderRecords collection.

UI Widget Class Hierarchy

We've given a good review of the View Models, now let us look at the UI classes and the XAML code.

We'll start by showing the class hierarchy diagram for Widget UI classes and by giving brief overview of every class within it.

Image 10

Here a brief description of the UI classes within this hierarchy (more detailed explanations will follow below).

 

  • WidgetBase class provides plumbing for communications with the View Model. It has several properties for defining a Widget's header. It's XAML Style/Template takes care of displaying different screens for different Widget's state ('Loading', 'Loaded', 'Exception) and for displaying the Widget's header.
  • WidgetWithContext<WidgetVMType> creates a View Model object of type WidgetVMType and sets the Widget's Content property to that object within the Widget's constructor. Also provides the LoadData() method that calls method of the same name on the View Model.
  • WidgetWithHighlightableDataGrid<WidgetVMType, RecordType> provides an extra property allowing to specify a collection of data grid columns.
  • BookOrdersWidget and BookReviewsWidget are simply concrete realization of WidgetWithHighlightableDataGrid<WidgetVMType, RecordType> with WidgetVMType and RecordType generic arguments set to concrete types.

 

UI Widget Code and Styles

Now, we'll continue describing how the WPFWidgetizer's functionality is being used by our sample.

As was mentioned above, we use BookOrdersWidget to display the returned entries. This class inherits from WidgetWithHighlightableDataGrid<BookOrdersWidgetVM, BookOrderRecordVM> class, which in turn inherits from WidgetWithContext<WidgetVMType>. The latter class provides a way to initialize the View Model for the Widget. It extends WidgetBase class which is derived from WPF's ContentControl class.

As was shown above, WidgetBase is the topmost class of the hierarchy. It derives from WPF's ContentControl. It's Content property is set to the Widget's View Model, while its ContentTemplate (in XAML) to the Widget's DataTemplate. It provides some UI action plumbing (which will be explained later). It also has dependency properties WidgetCaption and ShowWidgetHeader which define what should be shown within the Widget's header and whether the Widget's header should be shown at all.

The most important dependency property of the class is WidgetDataContentTemplate. The value for this dependency property is usually provided within the Styles of the derived classes. WidgetDataContentTemplate defines the actual representation of the data successfully loaded into the Widget.

 

WidgetBase style is defined within Themes/WidgetStyles.xaml file of the WPFWidgetizer.GenericFramework.WidgetsAndAssemblies project. It sets the ContentTemplate property of the widget to the following:

XML
<DataTemplate>
    <Grid x:Name="TopLevelWidgetContentContainerPanel">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- Widget Header -->
        <Border x:Name="HeaderBorder" 
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                BorderBrush="Black"
                BorderThickness="1"
                Height="30"
                Visibility="{Binding Path=ShowWidgetHeader, 
                                     Converter={StaticResource TheBooleanToVisibilityConverter},
                                     RelativeSource={RelativeSource AncestorType=widgets:WidgetBase}}">
            <TextBlock x:Name="WidgetCaption"
                       Text="{Binding Path=WidgetCaption, 
                                      RelativeSource={RelativeSource AncestorType=widgets:WidgetBase}}"
                       FontSize="15" 
                       HorizontalAlignment="Left"
                       VerticalAlignment="Bottom"
                       Margin="10,0,0,3"/>
        </Border>

        <!-- Widget Content-->
        <Grid x:Name="WidgetContentPanel"
              Grid.Row="1">

            <!-- TheWidgetContent control displays when 
                 data is successfully loaded into the widget -->
            <ContentControl x:Name="TheWidgetContent" 
                            Content="{Binding}"
                            ContentTemplate="{Binding Path=WidgetDataContentTemplate, 
                                                      RelativeSource={RelativeSource AncestorType=widgets:WidgetBase}}"
                            VerticalAlignment="Stretch"
                            HorizontalAlignment="Stretch"
                            Visibility="Collapsed"/>

            <!-- ErrorDisplayPanel displays when 
                 data loading resulted in an exception -->
            <Grid x:Name="ErrorDisplayPanel"
                  Visibility="Collapsed">
                <TextBlock HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Foreground="Red"
                           FontSize="30"
                           TextWrapping="WrapWithOverflow"
                           Text="{Binding Path=LastDataLoadingException.Message}"
                           Margin="20"/>
            </Grid>

            <!-- LoadingPanel displays while 
                 the data is being fetched from the server/service -->
            <Grid x:Name="LoadingPanel"
                  Visibility="Collapsed">
                <TextBlock Text="Please wait while the data is loading"
                           Foreground="Black"
                           FontSize="30"
                           Margin="20"
                           TextWrapping="WrapWithOverflow"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"/>
            </Grid>
        </Grid>
    </Grid>
    <DataTemplate.Triggers>
        <!-- TheWidgetContent becomes visible after the 
             data is successfully loaded into the widget
             (Widget property TheLoadingState is set to 'Loaded')-->
        <DataTrigger Binding="{Binding Path=TheLoadingState}"
                     Value="Loaded">
            <Setter TargetName="TheWidgetContent"
                    Property="Visibility"
                    Value="Visible" />
        </DataTrigger>

        <!-- LoadingPanel becomes visible while the data
             is being fetched from the server 
             (corresponding to the 'Loading' state
              of the Widget) -->
        <DataTrigger Binding="{Binding Path=TheLoadingState}"
                     Value="Loading">
            <Setter TargetName="LoadingPanel"
                    Property="Visibility"
                    Value="Visible" />
        </DataTrigger>

        <!-- ErrorDisplayPanel becomes visible after
             data loading resulted in an exception
             (Widget state is 'Exception') -->
        <DataTrigger Binding="{Binding Path=TheLoadingState}"
                     Value="Exception">
            <Setter TargetName="ErrorDisplayPanel"
                    Property="Visibility"
                    Value="Visible" />
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

HeaderBorder elements defines the Header of the Widget. Its visibility and text are controlled by the Widget's ShowWidgetHeader and WidgetCaption dependency properties correspondingly.

Within the widget's body (defined by WidgetContentPanel Grid) there are three different parts TheWidgetContent ContentControl, ErrorDisplayPanel Grid and LoadingPanel Grid. They are never shown together, but each one corresponds to a Widget's state (defined by the Widget's TheLoadingState property).

  • TheWidgetContent is visible when the Widget's state is Loaded - meaning the call to the server/service successfully returned data. TheWidgetContent's Content property is bound to the Widget's View Model, while its ContentTemplate is bound to WidgetDataContentTemplate property provided at the Widget's level. Using this property, we can choose the DataTemplate for the Widget's data.
  • ErrorDisplayPanel is shown when data loading resulted in an exception (the Widget's state is Exception).
  • LoadingPanel is shown while the data is being loaded - the Widget's state is Loading. This panel displays the message "Please wait while the data is loading" that we observed above.

 

While running the sample above, we already showed the Widget's Loading and Loaded states. It is easy to test the Exception state also. We have to go to the MockupDataAccessor class and uncomment the line above the switch statement throwing an exception:

C#
//throw new Exception("This is an exception for testing the widget error messages");  

After re-running the application and clicking "Load Data" button, we get the following screen:

Image 11

Now, that we understand the mechanics of the Widget's states (provided by WidgetBase class and the corresponding Styles/Templates), let us talk about how the Widget's data content is displayed in case of successful data loading.

Here is the code for BookOrdersWidget class:

C#
public class BookOrdersWidget :
    WidgetWithHighlightableDataGrid<BookOrdersWidgetVM, BookOrderRecordVM>
{
}  

As you see - it is simply a realization of WidgetWithHighlightableDataGrid class with the Widget's View Model being BookOrdersWidgetVM and the row (record) View Model being BookOrderRecordVM.

WidgetWithHighlightableDataGrid<WidgetVMType, RecordType> has only one interesting dependency property: GridColumns:

C#
public IEnumerable<datagridcolumn> GridColumns {...}  
</datagridcolumn>

This property allows to specify the GridColumn collection at the Widget's level.

Here is the XAML code for WidgetWithHighlightableDataGrid Style located within "Themes/WidgetWithHighlightableDataGridStyles.xaml" file of the WPFWidgetizer.GenericFramework.WidgetsAndAssemblies project:

XML
<Style TargetType="widgets:WidgetBase"
       BasedOn="{StaticResource TheBaseWidgetStyle}"
       x:Key="WidgetWithHighlightableGridStyle">
    <Setter Property="WidgetDataContentTemplate">
        <Setter.Value>
            <DataTemplate>
                <uiControls:DataGridControl x:Name="PART_TheDataGrid"
                                          IsReadOnly="True"
                                          ItemsSource="{Binding Path=ItemsSource}"
                                          CanHighlight="{Binding Path=CanHighlight}"
                                          GridColumns="{Binding Path=GridColumns, RelativeSource={RelativeSource AncestorType=widgets:WidgetBase}}">
                    <uiControls:DataGridControl.RowStyle>
                        <Style TargetType="DataGridRow"
                               BasedOn="{StaticResource BaseDataGridRowStyle}">
                            <Setter Property="uiControls:AttachedProperties.IsRowHighlighted"
                                    Value="{Binding Path=IsHighlighted}" />
                        </Style>
                    </uiControls:DataGridControl.RowStyle>
                </uiControls:DataGridControl>
            </DataTemplate>
        </Setter.Value>
    </Setter>
</Style>  

Note, that the TargetType of the Style is WidgetBase, not WidgetWithHighlightableDataGrid<WidgetVMType, RecordType> as one would expect. This is because compiled XAML still cannot handle the generics, so we are forced to specify a non-generic superclass as the TargetType.

We use the Widget's dependency property WidgetDataContentTemplate explained above, to specify the data template for the Widget's data.

Inside the DataTemplate you can see DataGridControl object that displays the grid. GridColumns property of the DataGridControl object is bound to the GridColumns property of the Widget, so that we can specify the collection of GridColumns at the Widget's level.

DataGridControl class is defined under WPFWidgetizer.GenericFramework.UIControls project. It extends WPF's DataGrid class and it defines two dependency properties: CanHighlight (that controls whether the data grid's rows can be highlighted) and GridColumns property.

We need GridColumns property since the DataGrid's built-in Columns property is read-only and cannot become a WPF binding's target. We use GridColumn property of the DataGridControl to modify the Columns collection. GridColumn as readable/writable dependency property can be made a WPF binding's target.

The Style for BookOrdersWidget is defined within "BookOrderWidgetStyles.xaml" file under WPFWidgetizer.BookStoreSpecificFramework.BookStoreWidgets project. It is based on WidgetWithHighlightableGridStyle and defines the GridColumns we want to show within the widget:

XML
<Style TargetType="bookStoreWidgets:BookOrdersWidget"
       BasedOn="{StaticResource WidgetWithHighlightableGridStyle}">
    <Setter Property="GridColumns">
        <Setter.Value>
            <x:Array Type="DataGridColumn">
                <DataGridTextColumn Header="Code"
                                    Binding="{Binding Path=BookCode}" />
                <DataGridTextColumn Header="Title"
                                    Binding="{Binding Path=BookTitle}" />
                <DataGridTextColumn Header="Number Copies Ordered"
                                    Binding="{Binding Path=NumberCopies}" />
                <DataGridTextColumn Header="Order Price"
                                    Binding="{Binding Path=TotalOrderPrice, StringFormat=F2}" />
            </x:Array>
        </Setter.Value>
    </Setter>
</Style>  

With this we complete our review of the first sample (as well as of most of the WPFWidgetizer code).

Simple Widget Assembly Consisting of Two Widgets with Inter-Widget Communications Sample

Sample Introduction

This sample is located within SimpleWidgetAssemblySample.sln solution under "WPFWidgetizer/Samples/SimpleWidgetAssemblySample" folder. The main project of the sample is WPFWidgetizer.Samples.SimpleWidgetAssemblySample.

While the purpose of the previous sample was to show the generic code structure of the package and demonstrate how the data is fetched from a service into the widget, this sample is primarily concentrated on describing the mechanism for sending action requests from within the widget and communicating between the widgets.

Another purpose of this sample is to show how one can use WPFWidgetizer code to arrange several widgets to work together within the same Widget Assembly. Note, that only arranging of the Widgets will require a custom XAML file - the communications between the Widgets are generic and do not require any extra code specific to this Widget Assembly.

Running the Sample

After starting the sample, you'll see the following screen:

Image 12

Now, press "Load Data from Server" button. For several seconds you'll be seeing the following:

Image 13

Finally, after the data loads, here what you'll see:

Image 14

We have a table of book reviews and a table of book orders displayed next to one another. If you click on a row in one of these tables, the rows corresponding to the same book in the other table will be highlighted in pink:

Image 15

Clicking a different row in the reviews grid would result in a change the set of highlighted rows in the orders grid. Similarly, clicking on one of the rows within orders grid, will result in rows corresponding to the same book being highlighted in the reviews grid.

Some Architectural Challenges

Achieving the behaviors demonstrated above presented several architectural challenges:

  1. The message is being sent from one widget to another when a row within DataGridControl is clicked. DataGridControl is a basic control from a basic project UIControls that should not know anything about Widgets or inter-Widget messaging. We need some kind of a plumbing to bring the action that occurred deep within the widget onto the Widget level before sending the corresponding message.
  2. What if there are more than two widgets within the Application. How do they know which widget should communicate to which? We need some kind of selective messaging between the widgets.

 

Bringing a Control's UI Action to the Widget's Level

In order to resolve the first architectural challenge listed above we defined the UIActionEvent bubbling routed event within UIRoutedEvents static class defined under UIControls project:

C#
public delegate void UIActionEventHandler(object sender, UIActionEventArgs e);

public static class UIRoutedEvents
{
    public static readonly RoutedEvent UIActionEvent =
        EventManager.RegisterRoutedEvent
        (
            "UIAction",
            RoutingStrategy.Bubble,
            typeof(UIActionEventHandler),
            typeof(UIRoutedEvents)
        );
}  

This event will fire when the corresponding user action occurs on a control and will bubble to the Widget's level to be handled by the Widget.

C# code for handling UIActionEvent is part of WidgetBase implementation:

C#
public WidgetBase()
{
    // add the handler to handle UIActionEvent coming from inside
    // the widget
    this.AddHandler(UIRoutedEvents.UIActionEvent, (UIActionEventHandler) OnUIActionHappened);
}

// widget's View Model should always be of WidgetCommunicationsBase
// class or one of its descendants. 
WidgetCommunicationsBase TheWidgetCommunicationsVM 
{
    get
    {
        return this.Content as WidgetCommunicationsBase;
    }
}

// UIActionEvent handler
void OnUIActionHappened(object sender, UIActionEventArgs e)
{
    // pass UIActionEventType and the payload object of the 
    // UIActionEvent to the widget's View Model to be 
    // handled there.
    TheWidgetCommunicationsVM.OnUIAction(e.TheUIActionType, e.Payload);
}  

We attach the OnUIActionHappened handler to the routed event within the WidgetBase() constructor. Within the handler's code, we simply pass the TheUIActionType and Payload properties of the event arguments to the Widget's View Model.

Let us take a look at the UIActionEvent's arguments. They are represented by UIActionEventArgs class defined within UIControls project:

C#
public class UIActionEventArgs : RoutedEventArgs
{
    public object Payload { get; set; }
    public UIActionType TheUIActionType { get; set; }
}  

Payload can be any object (in our case it is set to the View Model of the clicked row). UIActionType is an enumeration defined under GenericInfrastructure project, so that it can be available to both, the UI controls and View Models. At this point this enumeration has only two entries:

C#
public enum UIActionType
{
    Unknown,
    RowClick
}

Now, take a look at OnUIAction<T> method within WidgetCommunicationsBase class. This class, BTW, is the topmost class within Widget's View Model hierarchy and, as was mentioned above, it handles communications.

Here is the code for OnUIAction<T>:

C#
public void OnUIAction<T>(UIActionType uiActionType, T payload)
{
    // check if we have a handler for the coming uiActionType
    if (!_uiActionHandlers.ContainsKey(uiActionType))
        return; // if no handler - return

    Action<object> handler = _uiActionHandlers[uiActionType];

    // if a handler is found, run it passing the 
    // payload as its argument
    handler(payload);
}  

Dictionary _uiActionHandlers maps the UIActionTypes into handler delegates of type Action<object>:

C#
Dictionary<uiacti>> _uiActionHandlers =
    new Dictionary<uiacti>>();
</uiacti></uiacti>

If the UIActionType passed from the widget is contained in this _uiActionHandlers dictionary as a key, we pull out the handler value and call it, passing Payload to it as a parameter.

In order to set the mapping between a UIActionType and a handler, we use RegisterUIActionHandler<T> function:

C#
// set a handler for a UIActionType
public void RegisterUIActionHandler<T>(UIActionType uiActionType, Action<T> handler)
{
    _uiActionHandlers[uiActionType] = (obj) => handler((T) obj);
}  

In our case, the handler for RowClick UIActionType is being set within the constructor of the WidgetWithHighlightableItemsCollectionVM class:

C#
public WidgetWithHighlightableItemsCollectionVM()
{
    // register a handler for UIActionType.RowClick          
    RegisterUIActionHandler<RecordType>(UIActionType.RowClick, OnItemClicked);

    ...
} 

private void OnItemClicked(RecordType item)
{
    this.CanHighlight = false;
    this.Publish<object>(item, WidgetMessageType.Highlight);
}

As you can see, a row click results in two things happening:

  1. CanHighlight property on the View Model is set to false. This is done in order to remove highlighting from the data grid whose row was clicked.
  2. We publish an inter-widget message of WidgetMessageType.Highlight message type passing the data received from the UIActionEvent as a payload.

 

To complete this sub-section, let us show the mechanism by which we fire the UIActionEvent from the control. It is being attached to the grid rows via ClickRowBehavior within "Themes/DataGridControlStyles.xaml" resource file of WPFWidgetizer.GenericFramework.UIControls project:

XML
<uiControls:ClickRowBehavior x:Key="TheClickRowBehavior" />

<Style TargetType="DataGridRow"
       x:Key="BaseDataGridRowStyle">
    <!-- Set the ClickRowBehavior to fire UIActionEvent routed event
         when a row is clicked on the grid -->
    <Setter Property="uiControls:AttachedProperties.TheBehavior"
            Value="{StaticResource TheClickRowBehavior}" />
    <Style.Triggers>
        <!-- set the widget's background to pink 
             if IsRowHighlighted attached property on is true on the row
             and CanHighlight property is  true on the Widget -->
        <MultiDataTrigger>
            <MultiDataTrigger.Conditions>
                <Condition Binding="{Binding Path=(uiControls:AttachedProperties.IsRowHighlighted), 
                                             RelativeSource={RelativeSource Self}}"
                           Value="True" />
                <Condition Binding="{Binding Path=CanHighlight, 
                                             RelativeSource={RelativeSource AncestorType=uiControls:DataGridControl}}"
                           Value="True" />
            </MultiDataTrigger.Conditions>
            <Setter Property="Background"
                    Value="Pink" />
        </MultiDataTrigger>
    </Style.Triggers>
</Style>

The behaviors have been described in WPF Control Patterns. (WPF and XAML Patterns of Code Reuse in Easy Samples. Part 1). We attach our ClickRowBehavior via AttachedProperties.TheBehavior attached property. When attached to an object, the behavior's Attach(...) method is called assigning event handlers to the object's events. Here is the code for ClickRowBehavior class:

 

C#
public class ClickRowBehavior : IBehavior
{
    public void Attach(FrameworkElement element)
    {
        // on Attach, add a handler to the element's 
        // MouseLeftButtonUp event
        element.MouseLeftButtonUp += element_MouseLeftButtonUp;
    }

    void element_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        FrameworkElement element = (FrameworkElement) sender;

        // create UIActionEventArgs object
        UIActionEventArgs uiActionEventArgs = new UIActionEventArgs
        {
            TheUIActionType = UIActionType.RowClick,
            RoutedEvent = UIRoutedEvents.UIActionEvent
        };

        uiActionEventArgs.Payload = element.DataContext;

        // fire UIActionEvent to bubble up
        // the visual tree
        element.RaiseEvent(uiActionEventArgs);
    }

    public void Detach(FrameworkElement element)
    {
        // on Detatch, remove a handler from the element's 
        // MouseLeftButtonUp event
        element.MouseLeftButtonUp -= element_MouseLeftButtonUp;
    }
}  

Inter-Widget Communications

Now we are going to shed light on the WPFWidgetizer's mechanism for communications between various widgets.

As you rembember, we explained in the previous sub-section, how our row click event would result in Publish(...) method being called on the Widget's View Model (see the code for OnItemClick(...) function within WidgetWithHighlightableItemsCollectionVM class:

C#
private void OnItemClicked(RecordType item)
{
    this.CanHighlight = false;
    this.Publish<object>(item, WidgetMessageType.Highlight);
}  

Let us take a look at the implementation of Publish(...) function, located within WidgetCommunicationsBase class:

C#
public void Publish<T>
(
    T messagePayload,
    WidgetMessageType messageType,
    CommunicationsScopeType messageScopeType = CommunicationsScopeType.None,
    long targetWidgetID = -1
)
{
    long scopeItemID = -1;

    // if message scope is not None, get the message scope 
    // item id from _widgetScopeResolver
    if ( (_widgetScopeResolver != null) && 
         (messageScopeType != CommunicationsScopeType.None))
    {
        scopeItemID = _widgetScopeResolver.GetIDByScopeType(messageScopeType);
    }

    WidgetMessage<T> widgetMessage = new WidgetMessage<T>
    {
        SenderID = this.UniqueID,
        ScopeItemID = scopeItemID,
        TheMessageScopeType = messageScopeType,
        TargetWidgetID = targetWidgetID,
        MessagePayload = messagePayload,
        TheWidgetMessageType = messageType
    };

    EventAggregatorSingleton.Publish<WidgetMessage<T>>(widgetMessage);
}  

This function shows how the inter-Widget message is being formed.

Each Widget has its own unique id contained within its UniqueID property (coming, BTW, from IUniqueIDContainer interface that all the widgets implement). The Publish(...) function sets SenderID of the WidgetMessage object to be the UniqueID of the current Widget.

MessagePayload property of the WidgetMessage carries the data for the message.

TheWidgetMessageType property of the WidgetMessage can be used to narrow down the application of the current message - each widget can register to receive only messages of a certain WidgetMessageType. WidgetMessageType is an enumeration defined within WPFWidgetizer.GenericFramework.WidgetVMs project:

C#
public enum WidgetMessageType
{
    Unknown,
    Highlight,
    Navigate
}  

There are two modes of WidgetMessage propagation - Broadcast mode and Link mode. Both these modes will be discussed in more detail with more relevant samples in the follow-up article. Here, we are only going to give their brief overview.

Broadcast mode, will result in the WidgetMessage propagation to all the widgets within the Widget hierarchy (remember there can be Widgets containing Widget Assemblies and sub-Widgets and we are going to talk about them in the follow-up article). For broadcast mode, ScopeItemID property set on the WidgetMessage determines the topmost object under which all the widgets that share this object with the sender will receive the message from the sender Widget. If the ScopeItemID is -1, the message will be propagated to all the widgets within the application.

In Link mode, two widgets are linked and the sender Widget knows the UniqueID of the target Widget. Within WidgetMessage we set use TargetWidgetID property to specify it.

Of course, there can be combinations of the two approaches with a Widget sending messages to a target Widget within the same scope, assuming that other scopes within the Widget hierarchy might have the same UniqueID. This, BTW, can happen, if we have our link information serialized and restored. This will also be discussed in the follow-up article.

For our simple Widget Assembly example it is quite sufficient to keep TargetWidgetID set to -1 (meaning there is no linking and all the widgets can get the message).

Now, let us look at what happens on the receiving side of the inter-Widget messages.

We register our inter-Widget message handler within the constructor of the WidgetWithHighlightableItemsCollectionVM class:

C#
public WidgetWithHighlightableItemsCollectionVM()
{
    ...

    // register a handler for the Highlight message 
    // arriving from a different widget. 
    RegisterHandlerForMessageType<object>
    (
        WidgetMessageType.Highlight, 
        OnHighlightMessageArrived
    );
}  

OnHighlightMessageArrived(...) method is called upon arrival of WidgetMessage of WidgetMessageType.Highlight message type:

C#
private void OnHighlightMessageArrived(object item)
{
    if (ItemsSource == null)
        return;

    // user reflection base extension method ReflectionHelper.GetProp
    object highlightPropValue = item.GetProp(PropertyNameToHightlightOn);

    if (highlightPropValue == null)
        return;

    // set CanHighlight to true, so that 
    // the highlighting would show in the grid
    this.CanHighlight = true;

    // for each row set record.IsHighlighted to true
    // if the value of its property whose name is given by 
    // PropertyNameToHightlightOn, is the same as that 
    // of the incoming item.
    foreach (HighlightableBaseRecord record in this.ItemsSource)
    {
        object recordHighlightPropValue = record.GetProp(PropertyNameToHightlightOn);

        record.IsHighlighted = (recordHighlightPropValue == highlightPropValue);
    }
}  

PropertyNameToHightlightOn contains a name of the property whose value we compare in each record within the grid and from the incoming item object. If those values match, we highlight the row, otherwise we unhighlight it.

Function RegisterHandlerForMessageType that we used to associate OnHighlightMessageArrived(...) method with WidgetMessageType.Highlight is defined within WidgetCommunicationBase class:

C#
public void RegisterHandlerForMessageType<T>
(
    WidgetMessageType messageType, 
    Action<T> messageHandler
)
{
    // get a subscription token in order to 
    // be able to unsubscribe from the message type
    PrismUnsubscriber unsubscriber =
        EventAggregatorSingleton.Subscribe<WidgetMessage<T>>
    (
        (msg) => messageHandler(msg.MessagePayload),
        (msg) => FilterMsg<T>(msg, messageType)
    );

    // add the subscription token to _messageSubscriptions 
    // dictionary in order to be able to unsubscribe later
    List<PrismUnsubscriber> subscriptionsTokensForMessageType;
    if (!_messageSubscriptions.TryGetValue(messageType, out subscriptionsTokensForMessageType))
    {
        subscriptionsTokensForMessageType = new List<PrismUnsubscriber>();
        _messageSubscriptions[messageType] = subscriptionsTokensForMessageType;
    }

    subscriptionsTokensForMessageType.Add(unsubscriber);
} 

The subscription itself happens at the top of the function in the call to EventAggregatorSingleton.Subscribe(...) method. The rest of the function's body is related to storing the subscription tokens in order to be able to unsubscribe in the future.

There are two arguments in the call to EventAggregatorSingleton.Subscribe(...). The first argument is simply the message handler to be called when the message satisfying all the requirements arrives. The second argument is a predicate that we use to filter in only messages that satisfy all the requirements. As you can see, we pass method FilterMsg(...) as the second argument to this Subscribe call.

Here is the code for FilterMsg(...) function:

C#
bool FilterMsg<T>(WidgetMessage<T> msg, WidgetMessageType widgetMessageType)
{
    // if the widget is not active
    // it does not accept any messages.
    // return false (do not call the handler). 
    if (!IsActive)
        return false;

    // if the message's TheWidgetMessageType is not 
    // the same as that of the subscription, 
    // return false (do not call the handler). 
    if (msg.TheWidgetMessageType != widgetMessageType)
        return false;

    if (msg.TargetWidgetID >= 0)
    {
        // if the TargetWidgetID of the message
        // is value (>=0) and is not equal to the
        // UniqueID of the current widget,
        // return false (do not call the handler). 
        if (msg.TargetWidgetID != this.UniqueID)
            return false;
    }

    // if the SenderID of the widget is the same
    // as that of the current widget, 
    // return false (do not call the handler). 
    // this is because a widget cannot send 
    // messages to itself.
    if (msg.SenderID == this.UniqueID)
        return false;

    if ((msg.ScopeItemID != -1) && (msg.TheMessageScopeType != CommunicationsScopeType.None))
    {
        if (_widgetScopeResolver == null)
            return false;

        // if the msg.ScopeItemID is value (not -1) and 
        // its scope is not None, check that the current widget
        // belongs to the same scope. 
        long scopeID = _widgetScopeResolver.GetIDByScopeType(msg.TheMessageScopeType);

        if (scopeID != msg.ScopeItemID)
            return false;
    }

    return true;
}  

What the method does is highlighted in its comments.

  • First we check if the Widget is active, if not we return false and the handler is not called.
  • Then we check if the WidgetMessageType of the message and the current handler match. If not, we do not call the handler.
  • TargetWidgetID of the message being non-negative means that we are using the Link mode for the messaging and only the Widgets whose UniqueID coincides with the one of the message should fire their handlers.
  • If the ScopeItemID of the message is non-negative and TheMessageScopeType is not 'None' we make sure that the current widget belongs to the same scope (as will be explained in detail in the follow-up article).

 

Widget Assembly for BookOrdersWidget and BookReviewsWidget

Widget Assembly for our sample is being defined by a Style/Template within "Themes/BookReviewsAndOrdersAssemblyStyles.xaml" file under WPFWidgetizer.BookStoreSpecificFramework.BookStoreWidgets project:

XML
<Style x:Key="TheBookReviewsAndOrdersAssemblyStyle"
       TargetType="widgets:WidgetAssembly">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="widgets:WidgetAssembly">
                <Grid x:Name="PART_WidgetAssemblyPanel">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <bookStoreWidgets:BookReviewsWidget x:Name="TheBookReviewsWidget"
                                                        WidgetCaption="Book Reviews" 
                                                        ShowWidgetHeader="True"/>

                    <bookStoreWidgets:BookOrdersWidget x:Name="TheBookOrdersWidget" 
                                                       Grid.Column="1"
                                                       WidgetCaption="Book Orders"
                                                       ShowWidgetHeader="True"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>  

Note that its only purpose it to arrange the two widgets. It has no code specific to inter-Widget communications or interaction.

WidgetAssembly class is defined under WPFWidgetizer.GenericFramework.WidgetsAndAssemblies project:

C#
public class WidgetAssembly : Control
{
    Panel _widgetAssemblyPanel = null;

    public string WidgetAssemblyName
    {
        get;
        set;
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        _widgetAssemblyPanel = this.Template.FindName("PART_WidgetAssemblyPanel", this) as Panel;
    }

    // child widgets of the Widget Assembly
    public IEnumerable<WidgetBase> TheAssemblyWidgets
    {
        get
        {
            List<WidgetBase> result = new List<WidgetBase>();

            foreach (var child in _widgetAssemblyPanel.Children)
            {
                WidgetBase assemblyWidget = child as WidgetBase;

                if (assemblyWidget != null)
                    result.Add(assemblyWidget);
            }

            return result;
        }
    }

    // call the LoadData() method on each one of the 
    // child Widgets within the Widget Assembly. 
    public async Task LoadData()
    {
        Task[] loadDataTasks = this.TheAssemblyWidgets.Select((widget) => widget.LoadData()).ToArray();

        await Task.WhenAll(loadDataTasks);
    }
}  

TheAssemblyWidgets property enumerates the child Widgets of the Widget Assembly, while LoadData() method of the WidgetAssembly calls the LoadData() methods for all its child Widgets.

In the main project WPFWidgetizer.Samples.SimpleWidgetAssemblySample we simply display the Widget Assembly within a grid panel.

XML
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid>
        <widgets:WidgetAssembly Style="{StaticResource TheBookReviewsAndOrdersAssemblyStyle}"
                                x:Name="TheWidgetAssembly"/>
    </Grid>
    <Button x:Name="LoadDataButton"
            Grid.Row="1"
            Content="Load Data from Server"
            Width="200"
            Height="25"
            Margin="0,10"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"/>
</Grid>

LoadDataButton has its handler connected in MainWindow.xaml.cs file to call LoadData() method on the Widget Assembly:

C#
[Export]
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.LoadDataButton.Click += LoadDataButton_Click;
    }

    async void LoadDataButton_Click(object sender, RoutedEventArgs e)
    {
        await TheWidgetAssembly.LoadData();
    }
}  

Conclusion

In this installment of the article we introduced WPFWidgetizer framework based architecture and also the Widget Assembly pattern. We explained how they can be used for building flexible and extensible WPF applications fast.

In the follow-up article, we plan to cover

  1. Widgets that contain Widget Assembly with sub-widgets within them.
  2. Scope based hierarchical inter-Widget communications.
  3. Navigation between different Widget Assemblies.
  4. Element Factory Pattern.
  5. View Model Hierarchy Pattern.

 

License

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


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

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

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

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

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
GeneralMy vote of 5 Pin
Simon Slaven7-Oct-14 5:17
Simon Slaven7-Oct-14 5:17 
GeneralRe: My vote of 5 Pin
Nick Polyak15-Nov-14 16:17
mvaNick Polyak15-Nov-14 16:17 
QuestionVery good article! Pin
Volynsky Alex24-Sep-14 21:54
professionalVolynsky Alex24-Sep-14 21:54 
AnswerRe: Very good article! Pin
Nick Polyak27-Sep-14 14:31
mvaNick Polyak27-Sep-14 14:31 
GeneralRe: Very good article! Pin
Volynsky Alex28-Sep-14 10:44
professionalVolynsky Alex28-Sep-14 10:44 
GeneralMy vote of 5 Pin
MahBulgaria23-Sep-14 2:22
MahBulgaria23-Sep-14 2:22 
GeneralRe: My vote of 5 Pin
Nick Polyak23-Sep-14 12:47
mvaNick Polyak23-Sep-14 12:47 

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.