Code
There are several aspects of the StockViewer tutorial application that are quite interesting to review:
Background Updates
Broadcasting Messages
Modal User Interaction
DataBinding UI updates
Background Updates
In this application we have a static controller class StockManager that runs in the background in a single dedicated thread and uses the MarketService provider to query stock values.
The class is static (the code cannot create any instance of this class) and its functionality is shared by all users. Since the class is static we cannot use the constructor to start our background task, but we can use the static constructor, invoked the first time a user code references this class, to start our single shared task:
static StockManager()
{
// starts a background service listening for stock updates.
Task.Run(() => Listener());
}
StockPanel listens to the Update event and updates the browser through a push update when any value changes.
private void StockManager_Update(object sender, EventArgs e)
{
// this event may come from an out-of-bound thread, bring it in context
// and push the update to the browser.
Application.Update(this, () =>
{
// update only the stocks that have changed.
// var model = (BindingList<Stock>)this.bindingSource.DataSource;
var model = GetModel();
if (model == null)
return;
var changed = false;
foreach (var m in model)
{
if (m.Watching)
{
changed |= StockManager.UpdateQuote(m);
}
}
if (changed)
StockValuesChanged?.Invoke(this, EventArgs.Empty);
});
}
Broadcasting Messages
The method Listener() runs on its own thread. It's quite a simple method, in fact it's just a loop that periodically retrieves the stock quotes from the MarketService provider and broadcasts the Update and Error events.
// Task runner.
private async static void Listener()
{
while (true)
{
await Task.Delay(250);
try
{
LastQuotes = MarketService.GetQuotes();
// fire the update event to all listeners.
Update?.Invoke(null, EventArgs.Empty);
}
catch (Exception ex)
{
Error?.Invoke(null, new ThreadExceptionEventArgs(ex));
}
}
}
Modal User Interaction
The MarketService simulator may throw an exception when the StockManager controller asks for the stock symbols to watch:
public static Quote[] GetQuotes()
{
...
foreach (var s in SYMBOLS)
{
...
// random error, 1% of the times.
if (rand.Next(100) == 1)
throw new ApplicationException("Stock quotes are not available at this time.");
}
...
}
Since StockPanel uses StockManager.GetWatchableStocks() when it's displayed by handling the Appear event, if we are unable to get the list of stock symbols to watch we only have two options: try again or quit.
The cool and difficult part is the fact that this is a server-side process because the code has to run on the server to use a service that is not public and is not available to the whole world through a simple ajax call.
While running on the server, the code is able to ask the user on the client whether to continue or not and keep going within the same loop on the server! The result is a total server/client seamless integration that can be achieved with 1 line of code.
await MessageBox.ShowAsync(ex.Message, "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)
Data Binding UI Updates
We have seen that the StockViewer application needs to dynamically add panels to the DataRepeater to display the watchable stock symbols and to update the name, value and last update date for each stock being watched.
It's all done without any code. All the StockPanel does is to ask the StockManager to update the model:
foreach (var m in model)
{
if (m.Watching)
{
changed |= StockManager.UpdateQuote(m);
}
}
The method StockManager.UpdateQuote() doesn't know anything about the view. It simply sets the new value to the stock model Value property. If the value is different, the Stock model fires the PropertyChanged event.
public double Value
{
get { return _value; }
set
{
if (_value != value)
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
}
private double _value;
The data binding system handles the PropertyChanged event and updates all the bound properties causing the UI to get immediately refreshed.
Last updated
Was this helpful?