Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Sunday, August 5, 2012

The Case of a Strangely Coloured ProgressBar

At first this bug report puzzled me a bit. Essentially it said "Progress bar fills with blue rectangles, status text nearly impossible to read". That was a case of "works on my machine" because all I could see was that:

Can you see blue rectangles?

However, I soon discovered that just optimizing my Windows visual effects for best performance does a neat trick. If you don't know, it's under Control Panel → System → Advanced System Settings → Advanced → Performance → Settings → Visual Effects → Adjust for best performance, as in the screen shot below

Visual Effects

Here is what I saw:

Can you see blue rectangles NOW?

That was an easy fix. I just modified the ForeColor of the ProgressBar as shown below from the default value:

//before
<ProgressBar Name="Progress" Grid.Column="0" Value="{Binding ProgressValue}" HorizontalAlignment="Stretch"/>

//after
<ProgressBar Foreground="LightGreen" Name="Progress" Grid.Column="0" Value="{Binding ProgressValue}" HorizontalAlignment="Stretch"/>

Here is what I saw after making this change:

Adjusted for best performance

Allow Windows to determine settings

Reference:

WPF: Progressbar foreground colorby . Also posted on my website

Tuesday, July 31, 2012

Game of Life Exercise and Extension Methods

A quick attempt in writing the Game of Life simulator using WPF. As a side goal, I wanted to better understand the extension methods in C# so I tried to move as much code as possible into the Helper class without sacrificing readability.

PopulateGrid adds 50 rows and columns to a WPF Grid, creating a 50x50 matrix of cell. It then adds a rectangle to each cell so coloring could be applied. RePaintCell changes the background color of the cell at position i,j. InitializeArray just fills a 50x50 array of booleans, each value representing either a live or dead cell. CheckCell checks a single cell to find out if it will be live or dead in the next iteration. FillArray uses CheckCell to analyse the current array and construct the array of the next iteration. DrawArray compares the current and next iterations. If there is difference in the color of the cell at i,j, this cell is painted appropriately, otherwise it is skipped. Finally, AddGlider adds a glider element to the empty array so it was easy to test that the game runs correctly. The full listing is below.

MainWindow.xaml

<Window x:Class="GameOfLife.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="386" Width="525">
    <Grid Name="MainGrid">
        <Grid Name="DynamicGrid" ShowGridLines="True">
        </Grid>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="25,316,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System;

namespace GameOfLife
{
    public partial class MainWindow : Window
    {
        private bool[,] arrOne = new bool[Helper.x, Helper.y];
        private bool[,] arrTwo = new bool[Helper.x, Helper.y];
        private DispatcherTimer dt = new DispatcherTimer();
        private int count;

        public MainWindow()
        {
            InitializeComponent();
            dt.Interval = TimeSpan.FromMilliseconds(100);
            dt.Tick += dt_Tick;
            dt.Start();
            Loaded += MainWindow_Loaded;
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            DynamicGrid.PopulateGrid(new SolidColorBrush(Colors.Blue));
            InitializeGame();
        }

        void InitializeGame()
        {
            arrOne.InitializeArray(false);
            arrTwo.InitializeArray(false);
            arrOne.AddGlider(20, 30);
            arrOne.DrawArray(arrTwo, DynamicGrid);
        }

        void dt_Tick(object sender, EventArgs e)
        {
            if(true)
            {
                bool[,] arrCurrent = count%2 == 0 ? arrOne : arrTwo;
                bool[,] arrNext = count%2 == 0 ? arrTwo : arrOne;

                arrNext.FillArray(arrCurrent);
                arrNext.DrawArray(arrCurrent, DynamicGrid);
                count++;
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            if(dt.IsEnabled)
                dt.Stop();
            else
                dt.Start();
        }
    }
}

Helper.cs

using System.Linq;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;

namespace GameOfLife
{
    public static class Helper
    {
        public const int x = 50;
        public const int y = 50;

        public static void PopulateGrid(this Grid grid, SolidColorBrush brush)
        {
            for (int i = 0; i < y; i++)
            {
                grid.RowDefinitions.Add(new RowDefinition());
            }

            for (int j = 0; j < x; j++)
            {
                grid.ColumnDefinitions.Add(new ColumnDefinition());
            }

            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    Rectangle rect = new Rectangle();
                    rect.SetValue(Grid.RowProperty, i);
                    rect.SetValue(Grid.ColumnProperty, j);
                    rect.Fill = brush;
                    grid.Children.Add(rect);
                }
            }
        }

        public static void RePaintCell(this Grid grid, int i, int j, SolidColorBrush brush)
        {
            Rectangle cell = grid.Children.Cast<Rectangle>().First(r => Grid.GetRow(r) == j && Grid.GetColumn(r) == i);
            cell.Fill = brush;
        }

        public static void InitializeArray<T>(this T[,] arr, T value)
        {
            int iDim = arr.GetLength(0);
            int jDim = arr.GetLength(1);
            for (int i = 0; i < iDim; i++)
            {
                for (int j = 0; j < jDim; j++)
                {
                    arr[i, j] = value;
                }
            }
        }

        public static void AddGlider(this bool[,] arr, int x, int y)
        {
            arr[x - 1, y] = false;
            arr[x - 1, y + 1] = false;
            arr[x - 1, y + 2] = true;

            arr[x, y] = true;
            arr[x, y + 1] = false;
            arr[x, y + 2] = true;

            arr[x + 1, y] = false;
            arr[x + 1, y + 1] = true;
            arr[x + 1, y + 2] = true;
        }

        public static void FillArray(this bool[,] arr, bool[,]arrCurrent)
        {
            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    arr[i, j] = arrCurrent.CheckCell(i, j);
                }
            }
        }

        public static void DrawArray(this bool[,] arr, bool[,] arrCurrent, Grid grid)
        {
            SolidColorBrush redBrush = new SolidColorBrush(Colors.Red);
            SolidColorBrush blueBrush = new SolidColorBrush(Colors.Blue);

            for (int i = 0; i < y; i++)
            {
                for (int j = 0; j < x; j++)
                {
                    if (arr[i, j] != arrCurrent[i, j])
                    {
                        SolidColorBrush brush = arr[i, j] ? redBrush : blueBrush;
                        grid.RePaintCell(i, j, brush);
                    }
                }
            }
        }

        public static bool CheckCell(this bool[,] arr, int i, int j)
        {
            int nextI = i == (x - 1) ? 0 : i + 1;
            int prevI = i == 0 ? x - 1 : i - 1;
            int nextJ = j == (y - 1) ? 0 : j + 1;
            int prevJ = j == 0 ? y - 1 : j - 1;

            bool[] neighbours = new[]{
                                        arr[prevI, prevJ],   arr[i, prevJ],   arr[nextI, prevJ],
                                        arr[prevI, j],       arr[nextI, j],   arr[prevI, nextJ],
                                        arr[i, nextJ],       arr[nextI, nextJ]
                                    };

            int val = neighbours.Count(c => c);

            if (arr[i, j])
                return (val >= 2 && val <= 3) ? true : false;
            else
                return (val == 3) ? true : false;
        }
    }
}

Reference

Conway's Game of Life

The results are impressive

by . Also posted on my website

Wednesday, November 9, 2011

Flash in WPF Application the MVVM way (the easy part)

Now I have to use my WFPFlashLibrary I created in the last post in my main WPF view. I have to add the namespace for the project that contains my Flash control.

xmlns:Flash="clr-namespace:WPFFlashLibrary;assembly=WPFFlashLibrary"

I place my control in the WPF markup and bind Movie and Play.

<Grid>
<Flash:FlashControl Width="400" Height="400" Movie="{Binding Movie,UpdateSourceTrigger=PropertyChanged}" Play="{Binding Play,UpdateSourceTrigger=PropertyChanged}" />
</Grid>

This is the sample code which should be placed in the view. The Init will contain any code that needs to run on the ViewModel creation and will return the instance of the ViewModel. The PlayFlash will be then called right in the constructor of the view for simplicity, but of course it does not have to be there - it can be triggered whenever necessary.

public partial class TestFlashView : System.Windows.Controls.UserControl
{
public TestFlash(IUnityContainer container)
{
InitializeComponent();

DataContext = container.Resolve().Init(container);
(DataContext as TestFlashViewModel).PlayFlash();
}
}

And this is the implementation of the ViewModel. As soon as the PlayFlash() assigns values to the Movie and Play, the control will play the Flash animation (assuming the file is in the specified location!).

public class TestFlashViewModel : ViewModelBase
{
public TestFlashViewModel(IUnityContainer container):base(container)
{

}

virtual public TestFlashViewModel Init(IUnityContainer container)
{
//initialization - login etc.
return this;
}

//*****************************************************************************************

#region properties

string _movie;
public string Movie
{
get { return _movie; }
set { OnPropertyChanged(ref _movie , value,"Movie"); }
}

bool _play;
public bool Play
{
get { return _play; }
set { OnPropertyChanged(ref _play, value, "Play"); }
}

#endregion

public void PlayFlash()
{
Movie = @"c:\flash\flash.swf";
Play = true;
}
}

And that's the end of my small investigation. Unfortunately I found out that the plans have changed, the scope has been reduced and the flash movie is not required any longer. So I won't play with this control for anymore for now and move on to other things. Still was worth the effort.

by . Also posted on my website

Thursday, November 3, 2011

Flash in WPF Application the MVVM way (the hard part)

The Flash ActiveX control can not be added directly to the XAML file. Okay, the solution is well-known - just like with the Windows Forms control, you can use WindowsFormsHost to, well, host it - that's what the host is for. Then we add some code to the code-behind to load the movie and play it, and everything works. Right? Sort of. What if I'm trying my best to do the things the MVVM way? My code-behind file is usually empty, and all the business logic happens in the ViewModel. My XAML file does not have any button1_click event handlers, but rather is linked to the ViewModel by binding and is notified when something changes by means of OnPropertyChanged. What to do? The hard part is to come up with something that can be placed into the XAML file. The easy part is to place that something into the XAML file and bind it to the ViewModel. I'll start with the hard part, and use Visual Studio 2010.

Let's assume that the user controls are in a separate project. So I create a new project using the "WPF User Control Library" template and call it WPFControlLibrary. Let's delete UserControl1.xaml so it doesn't get in the way. First I'll add references to the COM components I may need.

Now I add a User Control (not the User Control - WPF yet!) and call it FlashWrapper. I add the AxShockwaveFlash control to it and call it axShockwafeFlash. For now let's worry only about loading and playing a movie. That's all the code I'll need:

using System.Windows.Forms;

namespace WPFControlLibrary
{
public partial class FlashWrapper : UserControl
{
public FlashWrapper()
{
InitializeComponent();
}

public void LoadMovie(string movie)
{
axShockwaveFlash.Movie = movie;
}

public void Play()
{
axShockwaveFlash.Play();
}

public void Stop()
{
axShockwaveFlash.Stop();
}
}
}

Now is the time to add the User Control - WPF. I call it FlashWPF.xaml. The XAML file is where the WindowsFormsHost comes to play - here I will host my FlashWrapper. Don't forget to add a reference to WindowsFormsIntegration!

<UserControl x:Class="WPFControlLibrary.FlashWPF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFControlLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid >
<WindowsFormsHost >
<WindowsFormsHost.Child>
<local:FlashWrapper x:Name="FlashPlayer" />
</WindowsFormsHost.Child>
</WindowsFormsHost>
</Grid>
</UserControl>

And still not much of C# code yet.

using System.Windows.Controls;

namespace WPFControlLibrary
{
public partial class FlashWPF : UserControl
{
public FlashWPF()
{
InitializeComponent();
}

public void LoadMovie(string movie)
{
FlashPlayer.LoadMovie(movie);
}

public void Play(bool value)
{
if (value)
FlashPlayer.Play();
else
FlashPlayer.Stop();
}
}
}

Last, and the most complex and cryptic step is to add a custom control. This will be a link between the MVVM application and the WPF control which hosts the wrapper which wraps the ActiveX control.

This is the ViewModel.
That is bound to the WPF custom control.
That hosts the C# wrapper.
That wraps the ActiveX control.
That plays the movie.

I think I got carried away a bit.

Ok, let's add a Custom Control - WPF and call it FlashControl. That's how it looks if all was done correctly:

public class FlashControl : Control
{
static FlashControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlashControl), new FrameworkPropertyMetadata(typeof(FlashControl)));
}
}

I have to modify it to expose three DependencyProperties: Movie, Play, and finally itself so it can be created in the MVVM application. And this is the end result:

public class FlashControl : Control
{
static FlashControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlashControl), new FrameworkPropertyMetadata(typeof(FlashControl)));
}

public FlashControl()
{
FlashPlayer = new FlashWPF();
}

//*****************************************************************************************

//Movie property definition

public static readonly DependencyProperty MovieProperty = DependencyProperty.RegisterAttached("Movie", typeof(string), typeof(FlashControl), new PropertyMetadata(MovieChanged));

private static void MovieChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).Movie = (string)e.NewValue;
}

//Play movie property definition
public static readonly DependencyProperty PlayProperty = DependencyProperty.RegisterAttached("Play", typeof(bool), typeof(FlashControl), new PropertyMetadata(PlayChanged));

private static void PlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).Play = (bool)e.NewValue;
}

//Flash player WindowFormHost
public static readonly DependencyProperty FlashPlayerProperty = DependencyProperty.RegisterAttached("FlashPlayer", typeof(FlashWPF), typeof(FlashControl), new PropertyMetadata(FlashPlayerChanged));

private static void FlashPlayerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as FlashControl).FlashPlayer = (FlashWPF)e.NewValue;
}

//*****************************************************************************************

public string Movie
{
get { return (string)this.GetValue(MovieProperty); }
set
{
this.SetValue(MovieProperty, value);
FlashPlayer.LoadMovie(value);
}
}

public bool Play
{
get { return (bool)this.GetValue(PlayProperty); }
set
{
this.SetValue(PlayProperty, value);
FlashPlayer.Play(value);
}
}

public FlashWPF FlashPlayer
{
get { return (FlashWPF)this.GetValue(FlashPlayerProperty); }
set { this.SetValue(FlashPlayerProperty, value); }
}
}

And the XAML file (which is for some reason called Generic.xaml and also when I tried to rename it I started getting errors, so I decided to leave the name alone) - I modified it slightly, but that's a matter of personal preference:

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFControlLibrary">
<Style TargetType="{x:Type local:FlashControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlashControl}">
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ContentControl Content="{TemplateBinding FlashPlayer}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

And the hard part is over!

by . Also posted on my website

Tuesday, October 25, 2011

Setting up a Composite WPF Application Properly.

It's time to step back from working on bits and pieces of something large and remind myself how to create a MVVM project from scratch. There are probably templates available now but I'll start with an empty C# WPF project and call it MVVM for simplicity. Renamed MainWindow to Shell everywhere in the solution because Shell is the conventional name for a top-level window in the application built in the Composite Application Library.

Composite Application Library displays and hides controls through the use of Regions. Several controls can be displayed as regions, one of them is ItemsControl.

An attached property RegionManager.RegionName indicates which region is associated with the control. So, in the Shell.xaml I replaced the Grid element with the ItemsControl element:

<ItemsControl Name="MainRegion" cal:RegionManager.RegionName="MainRegion"/>

For the code to compile, the following line has to be added to the Window tag

xmlns:cal="http://www.codeplex.com/CompositeWPF"

and the Microsoft.Practices.Prism dll has to be referenced in the project which contains the definition of RegionManager.RegionName

The bootstrapper initializes the application build using the Composite Application Library. At this point the bootstrapper only returns the new instance of a Shell class. Added the Bootstrapper class to the solution:

class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return this.Container.Resolve();
}

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

App.Current.MainWindow = (Window)this.Shell;
App.Current.MainWindow.Show();
}

protected override void ConfigureModuleCatalog()
{
}
}

At this point, the reference to Microsoft.Practices.Prism.UnityExtensions is required (for UnityBootstrapper). And not to forget the

using Microsoft.Practices.Unity;

otherwise the container would not resolve! Basically, Container.Resolve constructs an instance of the concrete class, resolving any dependencies that it has.

Now the bootstrapper has to run when the application starts. To achieve this, the Startup event of the application is handled in the App.xaml.cs file.

public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}

And since the instance of the Shell is manually created by the bootstrapper, the StartupUri attribute is not needed anymore in the App.xaml Application tag.

<Application x:Class="MVVM.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>

</Application.Resources>
</Application>

The application is fully functional now!

References:

WPF Hands-On Lab: Getting Started with the Composite Application Library

What does this mean in Prism/Unity: Container.Resolve()

UnityBootstrapper Class

Bootstrapper

by . Also posted on my website

Sunday, October 16, 2011

WPF Commands Part 1

I'm working on deeper understanding on how commands work in WPF. How do RoutedCommands work? First, I add a class to my solution and call it Commands.

public static class Commands
{
public static readonly RoutedCommand MyClick = new RoutedCommand();
}

Next, I declare the command and add a binding in my User Control.

<Button 
Command="{x:Static my:Commands.MyClick}"
Grid.Column="2" Height="23" HorizontalAlignment="Left" Margin="0"
Name="button1" VerticalAlignment="Top" Width="75" Content="Button"/>

<UserControl.CommandBindings>
<CommandBinding
Command="{x:Static my:Commands.MyClick}"
CanExecute="CommandBinding_CanExecute"
Executed="CommandBinding_Executed">
</CommandBinding>
</UserControl.CommandBindings>

Next, I add the handlers for the CommandBinding_CanExecute and CommandBinding_Executed.

private void CommandBinding_CanExecute(object sender, System.Windows.Input.CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}

private void CommandBinding_Executed(object sender, System.Windows.Input.ExecutedRoutedEventArgs e)
{
System.Windows.Forms.MessageBox.Show("Executed in DimensionsView");
}

So far, so good - the button is clicked, the message appears. However, that's too simple - next, I need to make sure my other user control will be notified when the button is clicked. This is the bit I was not able to do quickly on my first attempt. It appears that I need to implement a MVVM pattern to achieve this.

Fist I will implement the pattern in the simplest possible way. I'll create a ViewModel for my User Control. The ViewModel provides the mechanics of creating a command. The two functions serve the following purpose: CanClick() checks if the button click can happen. Some logic can be placed there and, if it returns false, the button will in fact be disabled. DoClick() actually runs the code that should happen on button click.

public class DimensionsViewModel
{
private DelegateCommand _clickCommand;

public ICommand ClickCommand
{
get
{
if (_clickCommand == null)
{
_clickCommand = new DelegateCommand(new Action(DoClick), new Func(CanClick));
}
return _clickCommand;
}
}

private bool CanClick()
{
/* code to check if the button can be clicked */
return true;
}

private void DoClick()
{
System.Windows.Forms.MessageBox.Show("Click in DimensionsView");
}
}

The Button in the xaml file needs to know which command to run.

<Button 
Command="{Binding Path=ClickCommand}"
Grid.Column="2" Height="23" HorizontalAlignment="Left" Margin="0"
Name="button1" VerticalAlignment="Top" Width="75" Content="Button"/></pre><p>And finally, the User Control needs to know which ViewModel to use:</p><pre class="brush:xaml"><UserControl x:Class="DynamicGrid.DimensionsView"

...
xmlns:my="clr-namespace:DynamicGrid">
<UserControl.DataContext>
<my:DimensionsViewModel/>
</UserControl.DataContext>
...
</UserControl>

And this is it! What I have achieved so far: there is absolutely no code in the xaml.cs file. Here's proof, the whole contents of the DimensionsView.xaml.cs:

public partial class DimensionsView : UserControl
{
public DimensionsView()
{
InitializeComponent();
}
}

Now someone can work on the UI and edit the xaml file, and someone can work on the business logic and work with the ViewModel, and they would not even need to check out the same file and then merge their changes or bump into one another in any way.

by . Also posted on my website

Thursday, October 13, 2011

Using the EventAggregator

The EventAggregator is the pattern that may be used in multiple scenarios, but in my case I have a WPF application that uses several User Control. When an action happens in a particular User Control (i.e. a user clicks the button), another User Controls or several User Controls react (i.e. draw an image). At first it looked like Routed Events and Commands are the way to go but I found out that could not easily build a working prototype so I decided to learn them at a later stage.

I found the explanation and a sample implementation here:

Simple message-based Event Aggregator

My prototype included two user controls, and I used the implementation of IEventAggregator and ISubscription from the link above.

That's the solution structure:

The user control that publishes the event does it in the following way:

public partial class DimensionsView : UserControl
{
private EventAggregator _eventAggregator = null;

private EventAggregator EventAggregator
{
get
{
if (_eventAggregator == null)
{
_eventAggregator = new EventAggregator();
}
return _eventAggregator;
}
}

public DimensionsView()
{
InitializeComponent();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
//publish a message that will be subscribed to in another user control
EventAggregatorInstance.EAggregator.Publish(new BasicMessage("test"));
}

And the subscribing user control declares an action it's interested in explicitly and subscribes to it.

public partial class GridView : UserControl
{
private Action someAction;

private EventAggregator _eventAggregator = null;

private EventAggregator EvengAggregator
{
get
{
if (_eventAggregator == null)
{
_eventAggregator = new EventAggregator();
}
return _eventAggregator;
}
}

public GridView()
{
InitializeComponent();
someAction = message => Test(message);
var subscription = EventAggregatorInstance.singletonAggregator.Subscribe(someAction);
}

private void Test(BasicMessage msg)
{
PerformAction();
}

public void PerformAction()
{
System.Windows.Forms.MessageBox.Show("Action Performed");
}
}

That's pretty much all that's required to build a working prototype. Of course, an instance of the EventAggregator has to be created for the application to work. This can be done in multiple ways, I chose one of the simplest - a singleton.

public static class EventAggregatorInstance
{
public static EventAggregator EAggregator = null;

public static EventAggregator singletonAggregator
{
get
{
if (EAggregator == null)
{
EAggregator = new EventAggregator();
}
return EAggregator;
}
}
}

I haven't given up on the Routed Events and Commands, that's my next subject to explore!

by . Also posted on my website