Image may be NSFW.
Clik here to view. On the previous post in this series we looked into the DataModel component in our architecture in detail and defined an abstract DataModel base class to derive our models from. On this post we’ll implement a concrete data model to represent a stock’s value. Why stock? It’s an object with a changing value that requires our DataModel constantly refresh and keep its data “alive”, and it’s simple to implement which makes it a perfect example for our first DataModel. The first thing we’ll do when defining our Stock DataModel is abstract the data source. This way we can easily implement several data sources for fetching a stock’s data and instantiate the DataModel with the right one (for example, read from Yahoo at runtime, read from fake data source during unit testing):
/// <summary> /// Defines the interface allowing <see cref="StockDataModel"/> to read quotes from various providers. /// </summary> public interface IStockDataProvider { /// <summary> /// Gets a given stock symbol's (given by <paramref name="symbol"/>) data. /// </summary> /// <param name="symbol">The stock's symbol.</param> /// <param name="name">The stock's company name.</param> /// <param name="quote">The last stock's quote.</param> /// <param name="change">The stock's change value.</param> /// <param name="open">The stock's open value.</param> /// <returns><b>True</b> if data was retrieved successfully; otherwise, <b>False</b>.</returns> bool TryGetData(string symbol, out string name, out double quote, out double change, out double open); }
Now that we have our data source defined we can implement different stock data providers for our DataModel to consume. Now, lets go over the StockDataModel class:
public class StockDataModel : DataModel { private string _symbol; private IStockDataProvider _quoteProvider; public StockDataModel(string symbol, IStockDataProvider provider) { _symbol = symbol; _quoteProvider = provider; this.State = DataModelState.Fetching; // Queue a work item to fetch the symbol's data if (!ThreadPool.QueueUserWorkItem(new WaitCallback(FetchDataCallback))) { this.State = DataModelState.Invalid; } } public string Symbol { get { return _symbol; } }
Our StockDataModel constructor takes the stock symbol that the model represents and an IStockDataProvider to fetch the stock’s data from. We set the initial DataModel state to Fetching and queue a work item for a background thread to update our model with the stock’s data – company name, quote, change value and open value. If we fail to queue the work item than we put the model in an invalid state. Next, we need to define the properties exposed by StockDataModel for data binding.
public string Name { get { VerifyCalledOnUIThread(); return _name; } private set { VerifyCalledOnUIThread(); if (_name != value) { _name = value; OnPropertyChanged("Name"); } } } public double Quote { get { VerifyCalledOnUIThread(); return _quote; } private set { VerifyCalledOnUIThread(); if (_quote != value) { _quote = value; OnPropertyChanged("Quote"); } } } ...
We’re sign a private setter to update the property values and trigger a PropertyChanged event if required. You can also add calculated properties. For example:
public double ChangePercent { get { if (double.IsNaN(Change)) return double.NaN; if (double.IsNaN(Open)) return double.NaN; try { double change = (Change / Open) * 100; return change; } catch { return double.NaN; } } }
In this case, it is important to remember to trigger the property change event for ChangePercent too when the values it depends on change… Now for the implementation of the FetchDataCallback. This method will be called by a background thread to update the stock data. Since this method is called by a background thread we’re free to perform expensive operations, such as calling a web service to fetch the stock’s data from an online provider (like Yahoo).
private void FetchDataCallback(object state) { string fetchedName; double fetchedQuote; double fetchedChange; double fetchedOpen; if (_quoteProvider.TryGetData(_symbol, out fetchedName, out fetchedQuote, out fetchedChange, out fetchedOpen)) { this.Dispatcher.BeginInvoke( DispatcherPriority.ApplicationIdle, new ThreadStart( delegate { this.Name = fetchedName; this.Quote = fetchedQuote; this.Change = fetchedChange; this.Open = fetchedOpen; this.State = DataModelState.Active; })); } else { this.Dispatcher.BeginInvoke( DispatcherPriority.ApplicationIdle, new ThreadStart( delegate { this.State = DataModelState.Invalid; })); } }
On the previous post, on the WPF threading model overview we noted the following:
If only the creator of a DispatcherObject can access it, how can a background thread interact with the user? The background thread does not access the UI directly but it can ask the UI thread to perform a task on its behalf by registering work items to its Dispatcher using it’s Invoke (for a synchronous call that returns when the UI thread finished executing the delegate) or BeginInvoke methods (which runs asynchronously)
In the above code, after fetching the data on the _quoteProvider.TryGetData we need to communicate these changes back to the UI thread. We use the Dispatcher to set the new values for the DataModel properties which ensures that our property change events will be triggered on the UI thread.
Keeping the Data Alive
So far, our code only fetches the stock data once. Lets see what it takes make out DataModel keep its data alive.
protected override void OnEnabled() { _timer = new DispatcherTimer(DispatcherPriority.Background); _timer.Interval = TimeSpan.FromMinutes(5); _timer.Tick += delegate { ScheduleUpdate(); }; _timer.Start(); ScheduleUpdate(); } protected override void OnDisabled() { _timer.Stop(); _timer = null; } private void ScheduleUpdate() { VerifyCalledOnUIThread(); // Queue a work item to fetch the quote if (ThreadPool.QueueUserWorkItem(new WaitCallback(FetchDataCallback))) { this.State = DataModelState.Fetching; } }
The above code defines a timer that is active when the DataModel is Enabled. The timer calls ScheduleUpdate every 5 minutes to perform the same data update using a background thread logic we performed on our constructor. We’re using a DispatcherTimer so that the calls to ScheduleUpdate will be made using the Dispatcher’s thread (the UI thread) so that we can update the DataModel’s state without a hassle. If we had used System.Threading.Timer then ScheduleUpdate would be called on the timer’s thread requiring the use of Dispatcher.BeginInvoke to update the state…
That’s it…
We’ve got the basic DataModel implemented. You can using it in you’re XAML window to see it working… To get a basic XAML running you’ll need to define a content control:
<ContentControl x:Name="_content" />
And set its content to a StockDataModel instance on your codebehind:
_content.Content = new StockDataModel("AAPL", someProvider);
Then all you need to do is define a data template for the StockDataModel type to control it’s appearance. Here’s a simple template for example:
<DataTemplate x:Name="StockTemplate" DataType="{x:Type local:StockDataModel}"> <StackPanel Orientation="Horizontal" mdb:EnableModel.DataModel="{Binding}" Height="30px" Width="Auto" ClipToBounds="True"> <TextBlock Text="{Binding Name}" Foreground="#737271" Width="120" Padding="3,0,0,3" Style="{StaticResource StockText}" /> <TextBlock Text="{Binding Quote}" Foreground="#737271" Width="55" Padding="0,0,0,3" Style="{StaticResource StockText}" /> </StackPanel> </DataTemplate>
You can find the code discussed in this article plus my own implementation for an IStockDataProvider that reads stock data from Yahoo here: On the next post we’ll discuss DataModel unit testing and see how the StockDataModel tests are implemented.
Image may be NSFW.
Clik here to view.
Comments (5) imported from www.ekampf.com/blog/:
Sunday, March 30, 2008 10:45:52 PM (GMT Daylight Time, UTC+01:00)
Thanks for the series! Looking forward for the following parts. However, there’s a bug in the shown code as you cannot check if a value is NaN by comparing to double.NaN. You have to use double.IsNaN(…).
Use IsNaN to determine whether a value is not a number. It is not possible to determine whether a value is not a number by comparing it to another value equal to NaN.
Simon Monday, March 31, 2008 4:39:37 AM
(GMT Daylight Time, UTC+01:00)
Hey Simon, Thanks.
Fixing the code and the post…
Regards,
Eran
Friday, April 04, 2008 3:47:12 AM (GMT Daylight Time, UTC+01:00)
Very nice article series. Keep up the good work!
Wednesday, May 28, 2008 2:55:06 PM (GMT Daylight Time, UTC+01:00)
Really great series, very nicely done.
Question: Why call VerifyCalledOnUIThread() in the ScheduleUpdate method? Since you’re calling BeginInvoke on the dispatcher inside FetchDataCallback all should be well, right?
Mike
Thursday, May 29, 2008 11:41:31 AM (GMT Daylight Time, UTC+01:00)
Hi Mike,
Good question. Notice that besides calling queuing a work item that calls FetchDataCallback, the ScheduleUpdate method also updates the model’s State to DataModelState.Fetching when that work item is queued. Since we’re changing the actual model we need to make sure we’re doing it in the UI thread. Alternatively, we could have used a System.Threading.Timer to do the updates ScheduleUpdate() will be called on a background thread directly, but then we couldn’t set the model state to fetching. We’d have to send that back to the UI thread.
Regards,
Eran Kampf
The post Developing a Robust Data Driven UI Using WPF – Stock DataModel Sample appeared first on DeveloperZen.