Blog Archive

Thursday, July 24, 2014

Practical MVVM/RX WPF App


Building MVVM/Rx WPF need to glue together multiple interface and correctly hand off to Dispatcher:

(1) (engineering) Need to use nuget.config align with .sln so package managed at solution level 
          <add key="repositorypath" value="Packages" />

(2) (design pattern) View - ViewModel --VMController--Adapter -- Transport

(3) (engineering) Fluent API can easily glue DataContext to VM, VMController point to VM

    public class FluentFactory
    {
        public FluentFactory ViewModelController(Func<IViewModelController> viewModelControllerFactory)
        {
            var f = viewModelControllerFactory.Invoke();
            f.ViewModel = _viewModel;
            return this;
        }

        INotifyPropertyChanged _viewModel=null;
        public FluentFactory ViewModel(Func<INotifyPropertyChanged> viewModelFactory)
        {
            _viewModel = viewModelFactory.Invoke();
            return this;
        }

        public void View(Func<FrameworkElement> viewFactory)
        {
            FrameworkElement fe = viewFactory.Invoke();
            fe.DataContext = _viewModel;
        }
    }

(4) (MVVM) Module can resolve instance to inject into DockingViewManager
    public class TradingModule : IModule
    {
        IUnityContainer _container;
        public TradingModule(IUnityContainer container)
        {
            _container = container;
        }
        public void Initialize()
        {
            _container.RegisterInstance<ITransport>(new TradingTransport());
            _container.RegisterInstance<IAdapter>(new TradingAdapter());
            _container.RegisterInstance<IScheduler>(new NewThreadScheduler());
            _container.RegisterInstance<LocalScheduler>(DispatcherScheduler.Current);
        }
    }

(5) (Rx) DockingViewManager will Create VMController when Button Click ask for well known view and then start Observable sequence

    public class DockingViewManager : IDockingViewManager
    {
        public DockingViewManager(ITransport transport, IAdapter adapter, IScheduler scheduler, LocalScheduler dispatcher)

        public UserControl GetDockingView(WellknowViewName viewName)
        {
            FluentFactory f = new FluentFactory();
            if (viewName == WellknowViewName.DurationTraderView)
            {
                DurationTraderView durView = new DurationTraderView();

                f.ViewModel(() => new DurationTraderViewModel())
                .ViewModelController(() => CreateDurationTraderViewModelController())
                .View(() => durView);
                return durView;
            }

            if (viewName == WellknowViewName._5_10Yr)
            {
                _5_10YRView view = new _5_10YRView();

                f.ViewModel(() => new _5_10YRViewModel())
                .ViewModelController(() => Create_5_10YRViewModelController())
                .View(() => view);
                return view;
            }
            return new UserControl();
        }

        public DurationTraderViewModelController CreateDurationTraderViewModelController()
        {
            return new DurationTraderViewModelController(_transport,_adapter,_scheduler,_dispatcher);
        }
    }


    public class DurationTraderViewModelController : IViewModelController
    {
        public INotifyPropertyChanged ViewModel { get; set; }

        public DurationTraderViewModelController(ITransport transport, IAdapter adapter, IScheduler scheduler, LocalScheduler dispatcher)
        {
            transport.GetTradingObservables()
                .SubscribeOn(scheduler)
                .ObserveOn(dispatcher)
                .Subscribe(fSet => adapter.updater(fSet, ViewModel));
        }
    }

    public class TradingTransport : ITransport
    {
        public IObservable<IFieldDataSet> GetTradingObservables()
        {
            return Observable.Create<IFieldDataSet>((obsr) =>
            {
                return Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)).Select(i => new FieldDataSet()).Subscribe(obsr);
            });
        }
    }

Side Note--- OnPropertyChange("Cusip") is error prone with Hard-coded string. Better utilize Expression tree in C# 4.0
     
        SetProperty(ref _cusip, value,()=>Cusip);

        public void SetProperty<T>(ref T field, T value, Expression<Func<T>> exp)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return;
            field = value;
            if (PropertyChanged != null)
            {
                MemberExpression me = exp.Body as MemberExpression;
                if (me != null && me.Member != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(me.Member.Name));
            }
        }

Side Note --- Load Module by Config
  <modules>
    <module assemblyFile="Modules.dll" 
            moduleType="Modules.TradingModule, Modules, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
            moduleName="TradingModule" startupLoaded="true" />
  </modules>

    public class DemoUnityBootstrapper : UnityBootstrapper
    {
        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new ConfigurationModuleCatalog();
        }
    }

No comments: