Skip to content

Instantly share code, notes, and snippets.

@chris-ray
Created June 26, 2015 15:53
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save chris-ray/09b1f9d5850ca6e585f2 to your computer and use it in GitHub Desktop.
Save chris-ray/09b1f9d5850ca6e585f2 to your computer and use it in GitHub Desktop.
WPF + MVVM + Akka.NET Modular UI
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="akka" type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<akka>
<hocon>
<![CDATA[
akka {
actor{
deployment{
/ui{
dispatcher = akka.actor.synchronized-dispatcher
}
}
}
}
]]>
</hocon>
</akka>
</configuration>
<Application x:Class="akkawpftest1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Akka.Actor;
using Akka.Configuration;
using akkawpftest1.infrastructure;
using akkawpftest1.vm;
namespace akkawpftest1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
internal static MainVM mainvm;
protected override void OnStartup(StartupEventArgs e)
{
var actorWpfSystem = new ActorWpfSystem()
{
ActorSystem = ActorSystem.Create("sys"),
};
mainvm = new MainVM(actorWpfSystem);
var uiactor = actorWpfSystem.ActorSystem.ActorOf(Props.Create(() => new UIActor(actorWpfSystem, mainvm)), "ui");
actorWpfSystem.UIActor = uiactor;
actorWpfSystem.StockFetchActor = actorWpfSystem.ActorSystem.ActorOf(Props.Create(() => new StockFetchActor()), "stockfetchactor");
Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(25);
uiactor.Tell(new UIActor.AddTickerModuleMessage() { Ticker = "ticker" + i });
}
});
base.OnStartup(e);
}
}
class StockFetchActor : ReceiveActor
{
public class MonitorStockMessage
{
public string Symbol { get; private set; }
public MonitorStockMessage(string symbol)
{
Symbol = symbol;
}
}
public class FetchStockMessage
{
public string Symbol { get; private set; }
public FetchStockMessage(string symbol)
{
Symbol = symbol;
}
}
private readonly Dictionary<string, List<IActorRef>> _stockWatch = new Dictionary<string, List<IActorRef>>();
public StockFetchActor()
{
Receive<MonitorStockMessage>(x => Handle(x));
Receive<FetchStockMessage>(x => Handle(x));
}
Random r = new Random();
private void Handle(FetchStockMessage x)
{
if (_stockWatch.ContainsKey(x.Symbol))
{
var quote = Math.Floor(r.NextDouble() * 10000) / 100;
foreach (var actor in _stockWatch[x.Symbol])
actor.Tell(quote);
}
}
private void Handle(MonitorStockMessage x)
{
if (!_stockWatch.ContainsKey(x.Symbol))
{
_stockWatch[x.Symbol] = new List<IActorRef>();
_stockWatch[x.Symbol].Add(Context.Sender);
Context.System.Scheduler.ScheduleTellRepeatedly(0, 250, Self, new FetchStockMessage(x.Symbol), Self);
}
else
{
_stockWatch[x.Symbol].Add(Context.Sender);
}
}
}
public interface IActorWpfSystem
{
ActorSystem ActorSystem { get; }
IActorRef UIActor { get; }
IActorRef StockFetchActor { get; }
}
class ActorWpfSystem : IActorWpfSystem
{
public ActorSystem ActorSystem { get; set; }
public IActorRef UIActor { get; set; }
public IActorRef StockFetchActor { get; set; }
}
class UIActor : ReceiveActor
{
public class AddModuleMessage
{
public IActorRef Actor { get; set; }
public ModuleVM ViewModel { get; set; }
public AddModuleMessage(IActorRef actor, ModuleVM viewModel)
{
Actor = actor;
ViewModel = viewModel;
}
}
public class AddTickerModuleMessage
{
public string Ticker { get; set; }
}
private List<IActorRef> _moduleActors;
readonly IActorWpfSystem _actorWpfSystem;
readonly MainVM _mainvm;
public UInt64 _moduleNumber = UInt64.MinValue;
public UIActor(IActorWpfSystem actorWpfSystem, MainVM mainvm)
{
_actorWpfSystem = actorWpfSystem;
_mainvm = mainvm;
_moduleActors = new List<IActorRef>();
Receive<AddModuleMessage>(x => Handle(x));
Receive<AddTickerModuleMessage>(x => Handle(x));
}
protected override void PreStart()
{
var vm = new MainModuleVM(_actorWpfSystem, "Main");
var mainModuleActor = Context.System.ActorOf(Props.Create(() => new MainModuleActor(vm)), "mainmodule");
vm.Actor = mainModuleActor;
Handle(new AddModuleMessage(mainModuleActor, vm));
}
private void Handle(AddModuleMessage x)
{
_moduleActors.Add(x.Actor);
_mainvm.AddModule(x.ViewModel);
}
private void Handle(AddTickerModuleMessage x)
{
var title = string.Concat("ticker", _moduleNumber++);
var vm = new TickerModuleVM(_actorWpfSystem, title, x.Ticker);
var tickerModuleActor = Context.System.ActorOf(Props.Create(() => new TickerModuleActor(_actorWpfSystem, vm, x.Ticker)), title);
vm.Actor = tickerModuleActor;
_mainvm.AddModule(vm);
}
}
class ModuleActor : ReceiveActor
{
protected readonly ModuleVM _mvm;
public ModuleActor(ModuleVM mvm)
{
_mvm = mvm;
}
}
class MainModuleActor : ModuleActor
{
public MainModuleActor(ModuleVM mvm)
: base(mvm)
{
//Receive<
}
}
class TickerModuleActor : ModuleActor
{
readonly IActorWpfSystem _actorWpfSystem;
readonly TickerModuleVM _vm;
readonly string _symbol;
public TickerModuleActor(IActorWpfSystem actorWpfSystem, TickerModuleVM mvm, string symbol)
: base(mvm)
{
_actorWpfSystem = actorWpfSystem;
_vm = mvm;
_symbol = symbol;
Receive<double>(x => Handle(x));
}
protected override void PreStart()
{
_actorWpfSystem.StockFetchActor.Tell(new StockFetchActor.MonitorStockMessage(_symbol));
}
private void Handle(double x)
{
_vm.Price = x.ToString("c");
}
}
}
namespace akkawpftest1.vm
{
public abstract class VM : INotifyPropertyChanged
{
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); }
#endregion
}
public abstract class ModuleVM : VM
{
private string _Name;
public string Name { get { return _Name; } set { if (_Name != value) { _Name = value; RaisePropertyChanged("Name"); } } }
public IActorRef Actor { get; set; }
protected readonly IActorWpfSystem _actorWpfSystem;
public ModuleVM(IActorWpfSystem actorWpfSystem, string name)
{
_actorWpfSystem = actorWpfSystem;
_Name = name;
}
}
public class MainVM : VM
{
private ObservableCollection<ModuleVM> _Modules;
public IEnumerable<ModuleVM> Modules { get { return _Modules; } }
readonly IActorWpfSystem _actorWpfSystem;
public MainVM(IActorWpfSystem actorWpfSystem)
{
_actorWpfSystem = actorWpfSystem;
_Modules = new ObservableCollection<ModuleVM>();
}
public void AddModule(ModuleVM vm)
{
_Modules.Add(vm);
}
}
public class MainModuleVM : ModuleVM
{
private string _AddTickerSymbol;
public string AddTickerSymbol { get { return _AddTickerSymbol; } set { if (_AddTickerSymbol != value) { _AddTickerSymbol = value; RaisePropertyChanged("AddTickerSymbol"); } } }
private RelayCommand _SubmitCmd;
public ICommand SubmitCmd { get { return _SubmitCmd; } }
public MainModuleVM(IActorWpfSystem actorWpfSystem, string name)
: base(actorWpfSystem, name)
{
_SubmitCmd = new RelayCommand(DoAddModule, CanDoAddModule);
}
private void DoAddModule(object obj)
{
_actorWpfSystem.UIActor.Tell(new UIActor.AddTickerModuleMessage() { Ticker = AddTickerSymbol });
}
private bool CanDoAddModule(object obj)
{
return !string.IsNullOrWhiteSpace(AddTickerSymbol) && new System.Text.RegularExpressions.Regex("^[a-zA-Z]*$").IsMatch(AddTickerSymbol);
}
}
public class TickerModuleVM : ModuleVM
{
readonly string ticker;
public string Ticker { get { return ticker; } }
private string _Price;
public string Price { get { return _Price; } set { if (_Price != value) { _Price = value; RaisePropertyChanged("Price"); } } }
public TickerModuleVM(IActorWpfSystem actorWpfSystem, string name, string ticker)
: base(actorWpfSystem, name)
{
this.ticker = ticker;
}
}
}
namespace akkawpftest1.infrastructure
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
public class RelayCommand : ICommand
{
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
}
}
<UserControl x:Class="akkawpftest1.v.MainModuleView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Width="200">
<TextBlock>Add A Ticker:</TextBlock>
<TextBox Text="{Binding AddTickerSymbol, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Command="{Binding SubmitCmd}">Submit</Button>
</StackPanel>
</UserControl>
namespace akkawpftest1.v
{
using System.Windows.Controls;
public partial class MainModuleView : UserControl
{
public MainModuleView()
{
InitializeComponent();
}
}
}
<Window x:Class="akkawpftest1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:akkawpftest1.v"
xmlns:vm="clr-namespace:akkawpftest1.vm"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:MainModuleVM}">
<v:MainModuleView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TickerModuleVM}">
<v:TickerModuleView />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Modules}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
namespace akkawpftest1
{
using System.Windows;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = App.mainvm;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Akka" version="1.0.3" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" />
</packages>
<UserControl x:Class="akkawpftest1.v.TickerModuleView"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Width="200">
<TextBlock Text="{Binding Ticker}" />
<TextBlock Text="{Binding Price}" />
</StackPanel>
</UserControl>
namespace akkawpftest1.v
{
using System.Windows.Controls;
public partial class TickerModuleView : UserControl
{
public TickerModuleView()
{
InitializeComponent();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment