/// <summary> /// Renders the view based on the provided view model. /// </summary> /// <param name="viewModel"> /// The view model which must be reflected by the view. /// </param> private void Render(StampCollectionViewModel viewModel) { /* In this case, I use a mixture of WPF data binding paradigm and manual WPF element manipulation. The key * point is that this is merely an implementation detail of the view and uses view-specific idioms and * "technology". The view model is completely unaware of this, which is good, because usually GUI * frameworks are stateful and mutable. */ this.DataContext = viewModel; /* Some things are easier to make not using WPF data binding. The following fragment displays current * stamp display order using the corresponding radio buttons. */ // { none of the radio buttons is checked }, by intention if (viewModel.DisplayOrder.Equals(SortedValueHighToLow)) { this.rbSortHighToLow.IsChecked = true; } else if (viewModel.DisplayOrder.Equals(SortedValueLowToHigh)) { this.rbSortLowToHigh.IsChecked = true; } else if (viewModel.DisplayOrder.Equals(NotSorted)) { this.rbSortNone.IsChecked = true; } }
/// <summary> /// The view sends messages using this method. This method is also used for messages originating from the world /// of effectful computations. In a way, this class also belongs to that world... /// </summary> /// <param name="message"></param> internal void SendMessage(Message message) { // Invoke the dispatching function with current model, current view model and the message. var newMandVm = Dispatching.dispatch(this.model, this.viewModel, message); // Remember new model and view model as current model and view model, respectively. Observe, that this // class doesn't care whether the model or view model actually changed. this.model = newMandVm.Item1; this.viewModel = newMandVm.Item2; // If the model requested an effectful computation, perform it and route the message back to this function, // such that it will be passed to the dispatcher function, exactly the same way as for messages coming from // the view. var effect = newMandVm.Item3; if (effect.IsEffect) { var e = effect as Effect <Message> .Effect; ExecuteEffect(e.Item, this.SendMessage); // executes asynchronously (AE) } // Send the updated view model to the view. Property (AE) is crucial, because the GUI will not be blocked // and the view can assume any intermediate state defined by the view model, until the computation (if any) // is done. The example in this demo is simulated verification of stamp rareness, where the view displays a // "working on it" feed-back. this.viewModelSubject.OnNext(this.viewModel); }
/// <summary> /// Initializes the initial state of the application, consisting of the initial model and initial view model. /// </summary> /// <remarks> /// Note, that as this class is merely a "state holder", it does not create the initial model and view model /// itself. This happens in the "main" function of the application. /// </remarks> /// <param name="initialModel"> /// The initial model. /// </param> /// <param name="initialViewModel"> /// The initial view model. /// </param> internal ApplicationModel(StampCollection initialModel, StampCollectionViewModel initialViewModel) { this.model = initialModel; this.viewModel = initialViewModel; this.viewModelSubject = new Subject <StampCollectionViewModel>(); }
/// <summary> /// Initializes the initial state of the application, consisting of the initial model and initial view model. /// </summary> /// <remarks> /// Note, that as this class is merely a "state holder", it does not create the initial model and view model /// itself. This happens in the "main" function of the application. /// </remarks> /// <param name="initialModel"> /// The initial model. /// </param> /// <param name="initialViewModel"> /// The initial view model. /// </param> internal ApplicationModel(StampCollection initialModel, StampCollectionViewModel initialViewModel) { this.model = initialModel; this.viewModel = initialViewModel; this.viewModelSubject = new Subject<StampCollectionViewModel>(); }
/// <summary> /// Initializes the main view (window) and renders the initial view based on the initial view model. /// </summary> /// <param name="initialViewModel"> /// The initial view model. /// </param> /// <param name="applicationModel"> /// The object which provides updates of the view model and a way of sending messages. /// </param> public MainWindow(StampCollectionViewModel initialViewModel, ApplicationModel applicationModel) { InitializeComponent(); this.applicationModel = applicationModel; // Render the initial view. this.Render(initialViewModel); // (IR) // Subscribe to changes in the view model. Each time a new view model arrives, render it. this.applicationModel.ViewModel.Subscribe(this.Render); /* We could avoid initial explicit call for initial rendering (IR) by using some fancy Rx subject which * sends the initial model already upon subscription. I kept things simple here, because this is an * irrelevant implementation detail. */ }
/// <summary> /// The view sends messages using this method. This method is also used for messages originating from the world /// of effectful computations. In a way, this class also belongs to that world... /// </summary> /// <param name="message"></param> internal void SendMessage(Message message) { // Invoke the dispatching function with current model, current view model and the message. var newMandVm = Dispatching.dispatch(this.model,this.viewModel, message); // Remember new model and view model as current model and view model, respectively. Observe, that this // class doesn't care whether the model or view model actually changed. this.model = newMandVm.Item1; this.viewModel = newMandVm.Item2; // If the model requested an effectful computation, perform it and route the message back to this function, // such that it will be passed to the dispatcher function, exactly the same way as for messages coming from // the view. var effect = newMandVm.Item3; if (effect.IsEffect) { var e = effect as Effect<Message>.Effect; ExecuteEffect(e.Item, this.SendMessage); // executes asynchronously (AE) } // Send the updated view model to the view. Property (AE) is crucial, because the GUI will not be blocked // and the view can assume any intermediate state defined by the view model, until the computation (if any) // is done. The example in this demo is simulated verification of stamp rareness, where the view displays a // "working on it" feed-back. this.viewModelSubject.OnNext(this.viewModel); }