Showing posts with label MVVMLight. Show all posts
Showing posts with label MVVMLight. Show all posts

Tuesday, 3 April 2012

WPF Metro Shell

Here is an example of a WPF application with pluggable components for core business functionality.

The solution features the following frameworks:

MVMMLight, MahApps.Metro, Ninject, ServiceLocator, Newtonsoft.Json, Rx Extensions

 

Projects

The main projects prefixed MetroWpf have a core shell window, login page and menu system.

MetroWpf-projects

Login

image

 

image

NOTE:

This is very much a work in progress but as I keep getting side tracked on other projects I thought I’d release what I have so far.

Source code:  http://stevenhollidge.com/blog-source-code/MetroWpf-CTP-v0.1.zip

HG repo:  http://hg.assembla.com/silverbladetech/file/4b8b38d17d24/MetroWpf

Wednesday, 15 February 2012

WPF Metro MVVM Application

This project is designed to get developers up and running on their WPF Metro MVVM projects.

Screenshots

image

 ScreenShot1 ScreenShot2 ScreenShot3 ScreenShot4

Frameworks

This project combines two excellent existing frameworks within the community:

Name Developer Link Comments
MahApps.Metro Paul Jenkins

http://www.theleagueofpaul.com/metro

Metro style and themes
MVVM Light Toolkit Laurent Bugnion http://mvvmlight.codeplex.com/ Originally based on ideas and source code from Microsoft PRISM

Concepts

The application contains working examples of the following:

  • MVVM with INPC (INotifyPropertyChanged), Messenger with messages (for event notifications), RelayCommands are available to use
  • Metro layout, styles and theming, easing and sliding from “outside” the visual area – all thanks to MapApps, great library!
  • Two way data binding for a combo box to a Enum list

image

ViewModel Example

Source code 

http://stevenhollidge.com/blog-source-code/wpf-metro-mvvm-app-stockmarket.zip

Monday, 26 September 2011

Silverlight Testing Automation Tool

Unit testing out of the box with Silverlight is a real pain.  Luckily for us StatLight makes its a breeze and also provides continuous integration with Visual Studio and TeamCity.

My code below shows a nice example of an application using the MVVM Light framework.

How Does It Work?

StatLight is a console application that creates an in memory web server. It starts up a web browser that will request from the web server a page containing your test xap. By executing all the tests in the browser and communicating test results back to the web server. The console now has the ability to publish those results in manners not allowed in the Silverlight sandbox.

Visual Studio Integration (via ReSharper and AgUnit)

ScreenShot142

TeamCity Integration

ScreenShot137

TeamCity Configuration

ScreenShot139

Standard StatLight Console Output

ScreenShot141

Silverlight Test Project

Some interesting points about the Silverlight Unit Tests:

  • To use EnqueueCallback and EnqueueTestComplete you need to inherit from SilverlightTest, available within the Silverlight Toolkit SDK.
  • The method’s utilising these calls must be marked with the [Asynchronous] attribute.
  • You’ll often need to tailor your code to provide hooks for the unit tests.  For example adding an event after an action occurs so that TestMethod can subscribe to the event.

ViewModel

Downloads

http://statlight.codeplex.com

http://agunit.codeplex.com

http://stevenhollidge.com/blog-source-code/SilverlightUnitTestDemo.zip

Tuesday, 16 August 2011

Silverlight Application Framework

This custom framework contains the following functionality:

Client

  • Ribbon menu bar (from DivElements)
  • Navigation frames for the main container and content pages
  • MVVM architecture
  • MVVM Light Locator to make ViewModels available to Views
  • MVVM Light Messenger for content navigation, status updates and logout
  • MVVM Light RelayEvent for Commanding in View Models
  • MVVM Light EventToCommand for key press binding in ViewModels
  • Custom client-side session data management
  • Mockable dialog box service
  • Custom Silverlight “loader”

Server

  • WCF Services (not WCF RIA Services)
  • Custom ASP.NET Authentication and login UI
  • ASP.NET Authorisation to allow for both secure and unsecure services
  • Text to Path web service to generate Xaml Path data from any string & font

 

Silverlight Application Framework demo from Steven Hollidge on Vimeo.

Navigation with the Ribbon and MVVM Light Messenger

The Ribbon button has a tag property that has the desired Uri set as its content.

When the button is clicked, a message is sent that contains the desired Uri.

The messenger listener receives the message and calls the ContentFrame.NavigateTo method.

Click here to download the source code:

http://stevenhollidge.com/blog-source-code/SilverlightExampleApp.zip

PS    Prefer a Treeview Menu to the Ribbon?

If you prefer the style of a Tree View menu, which is free compared to the Ribbon which is licensed, you can check out my blog posting here.

http://stevenhollidge.blogspot.com/2011/06/treeview-menu-for-silverlight.html

[ScreenShot008%255B5%255D.png]

Wednesday, 8 June 2011

Silverlight High Frequency Services

I’ve written a quick Silverlight application that listens to a WCF Service streaming a fire hose of the latest Fx prices at a rate of 1,200 per minute.

The application showcases the MVVM pattern, makes use of MVVMLight Toolkit and features the cosmopolitan/metro theme.

ScreenShot072_thumb[5]

The Service

The WCF service exposes a pub/sub model with a call back for each new FX Rate price:

using System;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;

namespace PricingServiceHost
{
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
public class PricingService : IPricingService
{
static PricingService()
{
Task.Factory.StartNew(() =>
{
var factory = new PriceFactory();
while (true)
{
Thread.Sleep(50);
if (PriceUpdate == null) continue;
FxRate latestPrice = factory.GetNextPrice();

PriceUpdate(
null,
new PriceUpdateEventArgs
{
LatestPrice = latestPrice
});
}
});
}

static event EventHandler<PriceUpdateEventArgs> PriceUpdate;

IPricingServiceCallback _callback;

public void Subscribe()
{
_callback = OperationContext.Current.GetCallbackChannel<IPricingServiceCallback>();
PriceUpdate += PricingService_PriceUpdate;
}

public void UnSubscribe()
{
PriceUpdate -= PricingService_PriceUpdate;
}

void PricingService_PriceUpdate(object sender, PriceUpdateEventArgs e)
{
if (((ICommunicationObject)_callback).State == CommunicationState.Opened)
{
try
{
_callback.PriceUpdate(e.LatestPrice);
}
catch
{
UnSubscribe();
}
}
else
{
UnSubscribe();
}
}
}
}


The Model (M)

using System;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;

namespace MetroPricingSample.Models
{
public class DisplayFxRate : ObservableObject
{
public static ObservableCollection<DisplayFxRate> InitialRates = new ObservableCollection<DisplayFxRate>
{
new DisplayFxRate("AUD", (decimal) 0.93272),
new DisplayFxRate("BRL", (decimal) 1.58100),
new DisplayFxRate("CAD", (decimal) 0.97495),
new DisplayFxRate("CHF", (decimal) 0.83603),
new DisplayFxRate("CNY", (decimal) 0.15425),
new DisplayFxRate("EUR", (decimal) 0.68103),
new DisplayFxRate("GBP", (decimal) 0.60819),
new DisplayFxRate("INR", (decimal) 44.6300),
new DisplayFxRate("JPY", (decimal) 80.0032),
new DisplayFxRate("NZD", (decimal) 1.21847),
new DisplayFxRate("RUB", (decimal) 27.7411),
new DisplayFxRate("THB", (decimal) 0.03303),
new DisplayFxRate("ZAR", (decimal) 6.71610)
};

public DisplayFxRate() { }

public DisplayFxRate(string isoCode, decimal rate)
{
IsoCode = isoCode;
PreviousRate = rate;
CurrentRate = rate;
Updated = DateTime.Now;
}

public const string IsoCodePropertyName = "IsoCode";
private string _isoCode = string.Empty;
public string IsoCode
{
get { return _isoCode; }

set
{
if (_isoCode == value) return;
_isoCode = value;
RaisePropertyChanged(IsoCodePropertyName);
}
}

public const string PreviousRatePropertyName = "PreviousRate";
private decimal _previousRate = 0;
public decimal PreviousRate
{
get { return _previousRate; }

set
{
if (_previousRate == value) return;
_previousRate = value;
RaisePropertyChanged(PreviousRatePropertyName);
}
}

public const string CurrentRatePropertyName = "CurrentRate";
private decimal _currentRate = 0;
public decimal CurrentRate
{
get { return _currentRate; }

set
{
if (_currentRate == value) return;

_previousRate = _currentRate;
_currentRate = value;

RaisePropertyChanged(PreviousRatePropertyName);
RaisePropertyChanged(CurrentRatePropertyName);
RaisePropertyChanged(DeltaPropertyName);
RaisePropertyChanged(StatusPropertyName);
}
}


public const string DeltaPropertyName = "Delta";
public decimal Delta
{
get
{
decimal result;

if (PreviousRate == 0 || CurrentRate == 0)
result = 0;
else
result = Math.Round(((CurrentRate / PreviousRate) - 1), 2);

return result;
}
}

public const string StatusPropertyName = "Status";
public Status Status
{
get
{
Status status;
var delta = Delta;

if (delta > 0)
status = Status.Increase;
else if (delta < 0)
status = Status.Decrease;
else
status = Status.NoChange;

return status;
}
}

public const string UpdatedPropertyName = "Updated";
private DateTime _updated = DateTime.MinValue;
public DateTime Updated
{
get { return _updated; }

set
{
if (_updated == value) return;
_updated = value;
RaisePropertyChanged(UpdatedPropertyName);
}
}
}
}


The View (V)

<navigation:Page x:Class="MetroPricingSample.Views.Pricing"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:Converters="clr-namespace:MetroPricingSample.Converters"
d:DesignHeight="300"
d:DesignWidth="640"
DataContext="{Binding PricingViewModel, Source={StaticResource Locator}}"
Style="{StaticResource PageStyle}"
mc:Ignorable="d">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="60" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.Resources>
<Converters:BoolToSubscribedTextConverter x:Key="BoolToSubscribedTextConverter" />
<Converters:StatusToIconConverter x:Key="StatusToIconConverter" />
<Converters:IsoCodeToFlagConverter x:Key="IsoCodeToFlagConverter" />
<Converters:DateTimeToTimeConverter x:Key="DateTimeToTimeConverter" />
</Grid.Resources>

<StackPanel Grid.Row="0">
<Button x:Name="btnSubscribe"
Width="200"
Height="30"
HorizontalAlignment="Left"
Content="{Binding Subscribed, Converter={StaticResource BoolToSubscribedTextConverter}}"
Command="{Binding SubscriptionCommand, Mode=TwoWay}" />
<TextBlock x:Name="tbInfo" Height="30" Text="{Binding ErrorText}"/>

</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<TextBlock Text="Currency" Width="85" Margin="10,0,0,0"/>
<TextBlock Text="Previous" Width="100"/>
<TextBlock Text="Current" Width="100" />
<TextBlock Text="Delta" Width="160" />
<TextBlock Text="Updated" />
</StackPanel>
<ListBox x:Name="lbFxRates"
ItemsSource="{Binding Path=Rates}"
Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding IsoCode, Converter={StaticResource IsoCodeToFlagConverter}}" Width="20" Margin="10,0,0,0"/>
<TextBlock Text="{Binding IsoCode}" Width="50" Margin="10,0,0,0"/>
<TextBlock Text="{Binding PreviousRate}" Width="100" />
<TextBlock Text="{Binding CurrentRate}" Width="100" />
<TextBlock Text="{Binding Delta}" Width="100" HorizontalAlignment="Right" Margin="0,0,10,0" />
<Image Source="{Binding Status, Converter={StaticResource StatusToIconConverter}}" Width="20" />
<TextBlock Text="{Binding Updated, Converter={StaticResource DateTimeToTimeConverter}}" Width="200" Margin="30,0,0,0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</navigation:Page>


The View Model (VM)



The view model in the client currently uses a generated proxy for the WCF service, we I really don’t like, this will be removed in the next refactoring iteration.

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MetroPricingSample.Models;
using MetroPricingSample.ServiceReference1;

namespace MetroPricingSample.ViewModels
{
public class PricingViewModel : ViewModelBase
{
public ObservableCollection<DisplayFxRate> Rates { get; set; }

private const string SubscribedPropertyName = "Subscribed";
private bool _subscribed = false;

public bool Subscribed
{
get { return _subscribed; }

set
{
if (_subscribed == value)
{
return;
}

_subscribed = value;
RaisePropertyChanged(SubscribedPropertyName);
}
}

private const string ErrorTextPropertyName = "ErrorText";
private string _errorText = string.Empty;

public string ErrorText
{
get { return _errorText; }

set
{
if (_errorText == value)
{
return;
}

_errorText = value;
RaisePropertyChanged(ErrorTextPropertyName);
}
}


public ICommand SubscriptionCommand { get; set; }

private bool _subscriptionCommand_CanExecute = true;

private PricingServiceClient _client;

public PricingViewModel()
{
Rates = DisplayFxRate.InitialRates;

if (IsInDesignMode) return;

_client = new PricingServiceClient();
_client.SubscribeCompleted += _client_SubscribeCompleted;
_client.UnSubscribeCompleted += _client_UnSubscribeCompleted;
_client.PriceUpdateReceived += PriceUpdate;
SubscriptionCommand = new RelayCommand(SubscriptionCommand_Execute, () => _subscriptionCommand_CanExecute);
}

void SubscriptionCommand_Execute()
{
if (!Subscribed)
{
_client.SubscribeAsync();
}
else
{
_client.UnSubscribeAsync();
}

_subscriptionCommand_CanExecute = false;
}

void _client_UnSubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = false;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}

_subscriptionCommand_CanExecute = true;
}

void _client_SubscribeCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
Subscribed = true;
ErrorText = "";
}
else
{
ErrorText = "Unable to connect to service.";
}

_subscriptionCommand_CanExecute = true;
}

public void PriceUpdate(object sender, PriceUpdateReceivedEventArgs e)
{
if (Subscribed)
{
PriceUpdate(e.fxRate);
}
}

public void PriceUpdate(FxRate fxRate)
{
try
{
foreach (var rate in Rates.Where(rate => rate.IsoCode == fxRate.IsoCode))
{
rate.CurrentRate = fxRate.Rate;
rate.Updated = fxRate.Updated;
}
}
catch (Exception e)
{
//log here
}
}
}
}


Conclusion



The application currently uses MVVM Light on the client side and WCF for the server side service.  The GUI is struggling to keep up with the service so I plan to introduce the Telerik Grid Control to see how it deals with the updates.  The functionality currently provided by value converters will be moved out of the view and into the view model and I’ll also refactor the application to use a REST interface with Service Stack (written by Demis Bellot).



You can download the source code here:



https://github.com/stevenh77/MetroPricingSample



When you run the application, be sure to set the Web host and Service to both start up at runtime (right click on solution > Properties > Multiple Startup Projects > START both MetroPricingSample.Web and PricingServiceHost).



Note: As I’m looking purely at performance I’ve deliberately omitted tests and some of the error handling and graceful dereferencing, I’ll add these as I refactor later on.

Wednesday, 18 May 2011

MVVMLight Messenger for Loosely Coupled Events

The Messenger class within MVVMLight gives developers the ability to publish an application wide event using messages that allow a subscriber to react without either the publisher or subscriber knowing anything about each other.

As always with MVVMLight it’s really simple to do.

In this example, I wanted a user to be able to click on the main menu of my application, shown on the left hand side of the screenshot below, which would update the content pane on the right hand side.

image

The main menu is a user control whilst the content pane is a navigation frame located within the MainPage.xaml.

To allow two pieces of code to communicate with each other, without knowing anything about each other, I will use the Messenger class in 3 simple steps.

1 Create a strongly typed message

 

2 Register a subscriber to the message type

Register the MainPage as the subscriber so that when the message is received the Uri within the message will be passed to the NavigateTo method to update the content pane.

 

3 Lastly, publish the message

 

When the TreeView menu is clicked the code behind checks for a Uri within the selected TreeViewItem then creates a message containing the Uri and publishes it to your application.

Summary

Using messages within your application can be a great way for your components to communicate whilst maintaining a loosely coupled architecture. This is important because you don’t have any nasty hard coded dependencies. Hard coded dependencies result in brittle, inflexible systems that can be hard to extend in the future.

The Messenger class in MVVMLight provides similar functionality to the EventAggregator class in PRISM.

You can download the WCF RIA sample application here:

http://www.stevenhollidge.com/blog-source-code/Silverblade5-WCF-RIA-MVVMLight-Messenger.zip

Please note that for demo purposes only menu options “Home > Search” and “Client > Details” have views that can be displayed within the content pane.  All other menu options will return an error.