using System;using System.Collections.Generic;using System.Text;namespace ModelSample{ /// <summary> /// Interface for a provider of stock quotes. /// </summary> public interface IStockQuoteProvider { /// <summary> /// Get a quote. This function may block on slow operations like hitting the network. /// </summary> /// <param name="symbol">The stock symbol.</param> /// <param name="quote">The quote.</param> /// <returns>Whether we were able to get a quote.</returns> bool TryGetQuote(string symbol, out double quote); }}
using System;using System.Collections.Generic;using System.Text;namespace ModelSample{ /// <summary> /// Mock IStockQuoteProvider that alwyas returns 100. /// </summary> public class MockQuoteProvider : IStockQuoteProvider { public bool TryGetQuote(string symbol, out double quote) { quote = 100.0; return true; } }}
using System;using System.Collections.Generic;using System.Collections.ObjectModel;using System.Text;using System.Windows.Input;namespace ModelSample{ /// <summary> /// View model for the portfolio view /// </summary> public class PortfolioViewModel { public PortfolioViewModel(IStockQuoteProvider quoteProvider) { _quoteProvider = quoteProvider; _stockModels = new ObservableCollection<StockModel>(); _stockModels.Add(new StockModel("MSFT", _quoteProvider)); _addCommand = new AddCommand(this); _removeCommand = new RemoveCommand(this); } /// <summary> /// The list of StockModels for the page. /// </summary> public ObservableCollection<StockModel> Stocks { get { return _stockModels; } } /// <summary> /// A CommandModel for Add. The command parameter should be the symbol to add. /// </summary> public CommandModel AddCommandModel { get { return _addCommand; } } /// <summary> /// A CommandModel for Remove. The command parameter should be the StockModel to remove. /// </summary> public CommandModel RemoveCommandModel { get { return _removeCommand; } } /// <summary> /// Private implementation of the Add Command /// </summary> private class AddCommand : CommandModel { public AddCommand(PortfolioViewModel viewModel) { _viewModel = viewModel; } public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e) { string symbol = e.Parameter as string; e.CanExecute = (!string.IsNullOrEmpty(symbol)); e.Handled = true; } public override void OnExecute(object sender, ExecutedRoutedEventArgs e) { string symbol = e.Parameter as string; _viewModel._stockModels.Add(new StockModel(symbol, _viewModel._quoteProvider)); } private PortfolioViewModel _viewModel; } /// <summary> /// Private implementation of the Remove command /// </summary> private class RemoveCommand : CommandModel { public RemoveCommand(PortfolioViewModel viewModel) { _viewModel = viewModel; } public override void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = e.Parameter is StockModel; e.Handled = true; } public override void OnExecute(object sender, ExecutedRoutedEventArgs e) { _viewModel._stockModels.Remove(e.Parameter as StockModel); } private PortfolioViewModel _viewModel; } private ObservableCollection<StockModel> _stockModels; private CommandModel _addCommand; private CommandModel _removeCommand; private IStockQuoteProvider _quoteProvider; }}
using System;using System.ComponentModel;using System.Diagnostics;using System.Threading;using System.Windows.Threading;namespace ModelSample{ public class StockModel : DataModel { public StockModel(string symbol, IStockQuoteProvider quoteProvider) { _symbol = symbol; _quoteProvider = quoteProvider; } protected override void OnActivated() { VerifyCalledOnUIThread(); base.OnActivated(); _timer = new DispatcherTimer(DispatcherPriority.Background); _timer.Interval = TimeSpan.FromMinutes(5); _timer.Tick += delegate { ScheduleUpdate(); }; _timer.Start(); ScheduleUpdate(); } protected override void OnDeactivated() { VerifyCalledOnUIThread(); base.OnDeactivated(); _timer.Stop(); _timer = null; } private void ScheduleUpdate() { VerifyCalledOnUIThread(); // Queue a work item to fetch the quote if (ThreadPool.QueueUserWorkItem(new WaitCallback(FetchQuoteCallback))) { this.State = ModelState.Fetching; } } /// <summary> /// Gets the stock symbol. /// </summary> public string Symbol { get { return _symbol; } } /// <summary> /// Gets the current quote for the stock. Only valid if State == Active. /// </summary> public double Quote { get { VerifyCalledOnUIThread(); return _quote; } private set { VerifyCalledOnUIThread(); if (_quote != value) { _quote = value; SendPropertyChanged("Quote"); } } } /// <summary> /// Callback on background thread to fecth quote. /// </summary> private void FetchQuoteCallback(object state) { double fetchedQuote; if (_quoteProvider.TryGetQuote(_symbol, out fetchedQuote)) { this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate { this.Quote = fetchedQuote; this.State = ModelState.Valid; })); } else { this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate { this.State = ModelState.Invalid; })); } } private string _symbol; private double _quote; private IStockQuoteProvider _quoteProvider; private DispatcherTimer _timer; }}
<Window x:Class="ModelSample.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ModelSample" Height="300" Width="300" > <Grid> <ContentControl x:Name="_content" /> </Grid></Window>
using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Shapes;namespace ModelSample{ /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { public Window1() { InitializeComponent(); _content.Content = new PortfolioViewModel(new MockQuoteProvider()); } }}
<Application x:Class="ModelSample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ModelSample" StartupUri="Window1.xaml" > <Application.Resources> <!-- Data Template for individual stocks --> <DataTemplate DataType="{x:Type local:StockModel}"> <StackPanel Orientation="Horizontal" local:ActivateModel.Model="{Binding}"> <TextBlock Text="{Binding Symbol}" Width="100"/> <TextBlock Text="{Binding Quote}" /> </StackPanel> </DataTemplate> <!-- Definition of Portfolio view --> <DataTemplate DataType="{x:Type local:PortfolioViewModel}"> <DockPanel LastChildFill="True"> <TextBlock DockPanel.Dock="Top" TextAlignment="Center">Portfolio View</TextBlock> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> <TextBox Name="AddSymbol" Width="100" /> <Button Command="{Binding AddCommandModel.Command}" CommandParameter="{Binding Path=Text,ElementName=AddSymbol}" local:CreateCommandBinding.Command="{Binding AddCommandModel}" > Add </Button> <Button Margin="50,0,0,0" Command="{Binding RemoveCommandModel.Command}" CommandParameter="{Binding Path=SelectedItem,ElementName=StockList}" local:CreateCommandBinding.Command="{Binding RemoveCommandModel}" > Remove </Button> </StackPanel> <ListBox Name="StockList" ItemsSource="{Binding Stocks}" /> </DockPanel> </DataTemplate> </Application.Resources></Application>
using System;using System.Collections.Generic;using System.Text;using System.Windows.Input;namespace ModelSample{ /// <summary> /// Model for a command /// </summary> public abstract class CommandModel { public CommandModel() { _routedCommand = new RoutedCommand(); } /// <summary> /// Routed command associated with the model. /// </summary> public RoutedCommand Command { get { return _routedCommand; } } /// <summary> /// Determines if a command is enabled. Override to provide custom behavior. Do not call the /// base version when overriding. /// </summary> public virtual void OnQueryEnabled(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; e.Handled = true; } /// <summary> /// Function to execute the command. /// </summary> public abstract void OnExecute(object sender, ExecutedRoutedEventArgs e); private RoutedCommand _routedCommand; }}
using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Input;namespace ModelSample{ /// <summary> /// Attached property that can be used to create a binding for a CommandModel. Set the /// CreateCommandBinding.Command property to a CommandModel. /// </summary> public static class CreateCommandBinding { public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(CommandModel), typeof(CreateCommandBinding), new PropertyMetadata(new PropertyChangedCallback(OnCommandInvalidated))); public static CommandModel GetCommand(DependencyObject sender) { return (CommandModel)sender.GetValue(CommandProperty); } public static void SetCommand(DependencyObject sender, CommandModel command) { sender.SetValue(CommandProperty, command); } /// <summary> /// Callback when the Command property is set or changed. /// </summary> private static void OnCommandInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { // Clear the exisiting bindings on the element we are attached to. UIElement element = (UIElement)dependencyObject; element.CommandBindings.Clear(); // If we're given a command model, set up a binding CommandModel commandModel = e.NewValue as CommandModel; if (commandModel != null) { element.CommandBindings.Add(new CommandBinding(commandModel.Command, commandModel.OnExecute, commandModel.OnQueryEnabled)); } // Suggest to WPF to refresh commands CommandManager.InvalidateRequerySuggested(); } }}
using System;using System.ComponentModel;using System.Diagnostics;using System.Windows.Threading;namespace ModelSample{ /// <summary> /// Base class for data models. All public methods must be called on the UI thread only. /// </summary> public class DataModel : INotifyPropertyChanged { public DataModel() { _dispatcher = Dispatcher.CurrentDispatcher; this.State = ModelState.Invalid; } /// <summary> /// Possible states for a DataModel. /// </summary> public enum ModelState { Invalid, // The model is in an invalid state Fetching, // The model is being fetched Valid // The model has fetched its data } /// <summary> /// Is the model active? /// </summary> public bool IsActive { get { VerifyCalledOnUIThread(); return _isActive; } private set { VerifyCalledOnUIThread(); if (value != _isActive) { _isActive = value; SendPropertyChanged("IsActive"); } } } /// <summary> /// Activate the model. /// </summary> public void Activate() { VerifyCalledOnUIThread(); if (!_isActive) { this.IsActive = true; OnActivated(); } } /// <summary> /// Override to provide behavior on activate. /// </summary> protected virtual void OnActivated() { } /// <summary> /// Deactivate the model. /// </summary> public void Deactivate() { VerifyCalledOnUIThread(); if (_isActive) { this.IsActive = false; OnDeactivated(); } } /// <summary> /// Override to provide behavior on deactivate. /// </summary> protected virtual void OnDeactivated() { } /// <summary> /// PropertyChanged event for INotifyPropertyChanged implementation. /// </summary> public event PropertyChangedEventHandler PropertyChanged { add { VerifyCalledOnUIThread(); _propertyChangedEvent += value; } remove { VerifyCalledOnUIThread(); _propertyChangedEvent -= value; } } /// <summary> /// Gets or sets current state of the model. /// </summary> public ModelState State { get { VerifyCalledOnUIThread(); return _state; } set { VerifyCalledOnUIThread(); if (value != _state) { _state = value; SendPropertyChanged("State"); } } } /// <summary> /// The Dispatcher associated with the model. /// </summary> public Dispatcher Dispatcher { get { return _dispatcher; } } /// <summary> /// Utility function for use by subclasses to notify that a property has changed. /// </summary> /// <param name="propertyName">The name of the property.</param> protected void SendPropertyChanged(string propertyName) { VerifyCalledOnUIThread(); if (_propertyChangedEvent != null) { _propertyChangedEvent(this, new PropertyChangedEventArgs(propertyName)); } } /// <summary> /// Debugging utility to make sure functions are called on the UI thread. /// </summary> [Conditional("Debug")] protected void VerifyCalledOnUIThread() { Debug.Assert(Dispatcher.CurrentDispatcher == this.Dispatcher, "Call must be made on UI thread."); } private ModelState _state; private Dispatcher _dispatcher; private PropertyChangedEventHandler _propertyChangedEvent; private bool _isActive; }}
using System;using System.Collections.Generic;using System.Diagnostics;using System.Text;using System.Windows;namespace ModelSample{ /// <summary> /// Attached property that can be used to activate a model. /// </summary> public static class ActivateModel { public static readonly DependencyProperty ModelProperty = DependencyProperty.RegisterAttached("Model", typeof(DataModel), typeof(ActivateModel), new PropertyMetadata(new PropertyChangedCallback(OnModelInvalidated))); public static DataModel GetModel(DependencyObject sender) { return (DataModel)sender.GetValue(ModelProperty); } public static void SetModel(DependencyObject sender, DataModel model) { sender.SetValue(ModelProperty, model); } /// <summary> /// Callback when the Model property is set or changed. /// </summary> private static void OnModelInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { FrameworkElement element = (FrameworkElement)dependencyObject; // Add handlers if necessary if (e.OldValue == null && e.NewValue != null) { element.Loaded += OnElementLoaded; element.Unloaded += OnElementUnloaded; } // Or, remove if necessary if (e.OldValue != null && e.NewValue == null) { element.Loaded -= OnElementLoaded; element.Unloaded -= OnElementUnloaded; } // If loaded, deactivate old model and activate new one if (element.IsLoaded) { if (e.OldValue != null) { ((DataModel)e.OldValue).Deactivate(); } if (e.NewValue != null) { ((DataModel)e.NewValue).Activate(); } } } /// <summary> /// Activate the model when the element is loaded. /// </summary> static void OnElementLoaded(object sender, RoutedEventArgs e) { FrameworkElement element = (FrameworkElement)sender; DataModel model = GetModel(element); model.Activate(); } /// <summary> /// Deactivate the model when the element is unloaded. /// </summary> static void OnElementUnloaded(object sender, RoutedEventArgs e) { FrameworkElement element = (FrameworkElement)sender; DataModel model = GetModel(element); model.Deactivate(); } }}