WPF + MVVM + Akka.NET Modular UI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace akkawpftest1.v | |
{ | |
using System.Windows.Controls; | |
public partial class MainModuleView : UserControl | |
{ | |
public MainModuleView() | |
{ | |
InitializeComponent(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace akkawpftest1 | |
{ | |
using System.Windows; | |
public partial class MainWindow : Window | |
{ | |
public MainWindow() | |
{ | |
InitializeComponent(); | |
DataContext = App.mainvm; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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