Background
In software development we commonly come across scenarios which we might have implemented during different projects.Design patterns are solutions to such commonly recurring problems
and designers of Object Orient Programming have provided us various design patterns which enables us to solve specific problem in best possible way. In this article I will be showing
you the application of one of the most popular design pattern Observer
and its application to real life scneario. For more information on design patterns please refer this
link.
Observer is a design pattern which is also some times referred as Publisher/Subscriber pattern and is useful when various stakeholders may show interest in particular subject. They
can subscribe to it. The subject when it changes its state then it will notify its subscribers and they will update themeslevels.The scenario taken for the application used in this
article is Real Time StockFeed Dashboard which is of particular interest to stock brokers and investors who may be interested in monitoring in real time trend of key stocks of interest to them.
Introduction
Purpose of this article is to demonstrate the application of Observer Design pattern to real life scenario. In this article I have taken the example of Real Time Stock Feed Dashboard which
shows real time trend of four key stocks. I have taken three types of observers in this example who are interested in change in the StockFeed
and update or use them in different fashion.
Below list shows the three categories of observers used in this example
- Real Time Trend Control ( 4 Views)
- Rolling Display of Stock Feeds in Application Status Bar.
- StockFeedXML Exporter.This exports the stock feed in XML format.
This article will provide detailed implementation of this pattern using WPF and C#. In this article I will also explore following features of WPF
- Use of Different Layout Panels
- Data Binding
- Themes in WPF
- Graphics Capabilities in WPF
- Toolbar and use of command design pattern
- Lambda Expressions
Design Overview
Figure 1 shows the class diagram of the Real Time Stock Dashboard Application.Since most of the observers are updating the GUI Elements which is not allowed
to be updated from different thread and hence the change is indirectly notified to all observers. This is done during Dispatcher.Invoke
method and I
will cover the role of this method in detail in later section.
Figure 1: Basic Class Diagram of Demo Application
From the class diagram IObserver
is an interface which is implemented by all the observers TrendGraphControl
, StatusBoard
and
StockFeedXMLExporter
. StockFeedObservable
is a class which is responsible for publishing stockPriceChanged
event and all interested
observers will subscribe to this event. FeedGenerator
is a class which is responsible for simulating the feed and provides the new StockFeed
value.
StockFeedObservable
class runs a thread in background to receive the new StockFeed
value from FeedGenerator
class by invoking it's GetNextFeed()
method. StockFeedObservable
class raises the event whenever it receives the new feed and notifies all it's subscribers. StockFeedEventArgs
is a class which wraps the StockFeed
.
All the concrete observers then update themselves when they recieve notification.
Business Layer
StockFeed
is a main model of this application. All the observers are interested in this subject. I will explain detailed implementation of FeedGenerator
class which acts as a data source for providing a new feed.
Implemention of FeedGenerator
FeedGenerator
is a class which is responsible to provide stock feed for different stocks. For the purpose of this application I have simulated the data for the feed for
the key stocks with some fictitious data and source data for these key stocks is maintained in CSV files. FeedGenerator
class then read these files and maintains
them as a collection of StockFeed
entities. Since it is for four different stocks each of the collection is maintained in a dictionary. The GetNextFeed()
method then returns new value of StockFeed
from this collection. The implementation details are given below.
public class FeedGenerator
{
private string _filePath = string.Empty;
private Dictionary<string, List<StockFeed>> _stockFeed= new Dictionary<string,List<StockFeed>>();
private Dictionary<string, int> _stockIndexList = new Dictionary<string,int>();
private const string STOCK_FEED_PATH = @"\ObserverDemo\Stocks\";
public void InitFeed(string [] files) {
try
{
_filePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
foreach(string f in files){
_stockIndexList.Add(f, 0);
string path = _filePath+STOCK_FEED_PATH+f+".csv";
Logger.Log.Information(string.Format("Reading Stock Feed File: {0}", path));
int index = 0;
List<StockFeed> stockFeedList= new List<StockFeed>();
using (StreamReader sr = new StreamReader(path))
{
while (!sr.EndOfStream)
{
string line = sr.ReadLine();
StockFeed sf=null;
if(index >0){
sf = ExtractStockFeedFromLine(line);
stockFeedList.Add(sf);
}
index++;
}
}
_stockFeed.Add(f,stockFeedList);
Logger.Log.Information(string.Format("No of records processed from file {0} are {1}",f, stockFeedList.Count));
}
}catch(Exception ex){
string message ="Error occured while initialising the stock feed and error is " +ex.ToString();
Logger.Log.Error(message);
}
}
private StockFeed ExtractStockFeedFromLine(string line)
{
string[] aryValues = line.Replace("\"","").Split(',');
StockFeed sf = new StockFeed();
sf.StockCode = aryValues[0];
sf.StockFeedDate = DateTime.Now;
sf.MaxPrice = Decimal.Parse(aryValues[5]);
sf.MinPrice = Decimal.Parse(aryValues[6]);
sf.Price = Decimal.Parse(aryValues[8]);
return sf;
}
public StockFeed GetNextFeed(string stockCode) {
if (_stockIndexList.ContainsKey(stockCode))
{
int index = _stockIndexList[stockCode];
List<StockFeed> feedList = _stockFeed[stockCode];
if (index >= feedList.Count-1)
{
index = 0;
}
_stockIndexList[stockCode] = ++index;
StockFeed sf= feedList[index];
sf.FeedIndex = index;
return sf;
}
return null;
}
}
}
</returns>
From the above code you can see that the FeedGenerator
class defines following key fields. _stockFeed
is a dictionary maintaining the collection of StockFeed
initialised by reading from CSV files. _stockFeedIndexList
is the dictionary
maintaining the index of the feed last provided for each stocks. This class has one private method InitFeed()
which populates the dictionary. ExtractStockFeedFromLine()
is another private
method and it is a helper method to parse the line read from CSV and initialise StcokFeed
entity. This class exposes one public method GetNextFeed()
which provides the
new value of a feed.
Implementing StockFeedObservable
StockFeedObservable
class is responsible for publishing stockPriceChangedEvent
. It exposes following key methods Start()
and Stop()
.
The Start()
method spawns a thread in a background and gets new feed by invoking the GetNextFeed()
method of FeedGenerator
. It then raises the stockPriceChanged
event to notify
all the observers. Below code shows the implementation details. This class is implemented as a Singleton
.
public class StockFeedObservable
{
private static readonly StockFeedObservable _instance = new StockFeedObservable();
public event EventHandler<StockFeedEventArgs> stockPriceChanged;
private FeedGenerator fg = new FeedGenerator();
string[] _files = { "RELCAP", "ITC", "INFY", "TCS" };
private int _fileIndex=0;
private bool _simFlag = false;
System.Threading.Thread newTheread = null;
private StockFeedObservable() {
fg.InitFeed(_files);
}
public static StockFeedObservable Observer {
get { return _instance; }
}
public void Start()
{
newTheread= new System.Threading.Thread(simulateFeed);
_fileIndex = 0;
_simFlag = true;
newTheread.Start();
}
public void Stop() {
if (newTheread != null)
_simFlag = false;
}
private void simulateFeed(object obj)
{
while (_simFlag)
{
if (_fileIndex >= _files.Length)
{
_fileIndex = 0;
}
StockFeed sf = fg.GetNextFeed(_files[_fileIndex]);
StockFeedEventArgs args = new StockFeedEventArgs();
args.SF = sf;
args.StockCode = _files[_fileIndex];
OnPriceChanged(args);
_fileIndex++;
System.Threading.Thread.Sleep(100);
}
}
private void OnPriceChanged(StockFeedEventArgs args) {
if (stockPriceChanged != null)
stockPriceChanged(this,args);
}
}
The above code is self explanatory.
Implementing Observers
The detailed implementation of each type of observers is given below.
TrendGraphControl Implementation
The TrendGraphControl
is a conceptual class and since the GraphControl
class displays real time trend of stock feed, I have given it this name.
This control uses WPF GDI Graphics capabilities to display the chart control and plots the trend in real time. Since this is one of our observer type, hence it implements IObserver
interface. It implements Update()
method to plot the stock feed trend in real time. Below code shows the implementation of the key methods. This class is also derived from
UserControl
class.
public partial class GraphControl : UserControl,IObserver
{
public GraphControl()
{
InitializeComponent();
_dataPoints = new List<point>();
_orgDataPoints = new List<point>();
_minY = -40.0;
_maxY = 40.0;
}
public string StockCode { get; set; }
protected override void OnRender(DrawingContext drawingContext)
{
_drawingContext = drawingContext;
DrawGraph(drawingContext);
}
private void DrawLegend()
{
if (_orgDataPoints.Count == 0)
return;
double range = _maxY - _minY;
double step = range / 5.0;
double curValue = _minY;
for (int i = 0; i <= 5; i++)
{
if(i==5)
{
curValue = _maxY;
}
Point p = new Point();
p.X = _minX;
p.Y = curValue;
Point pt = TranslateToPixels(p);
pt.X = pt.X -40;
pt.Y = pt.Y - 15;
string curValueS = string.Format("{0:0.0}", curValue);
DrawFormattedText(curValueS, pt,9,Brushes.Blue);
curValue += step;
}
Point p1 = new Point();
p1.X = _maxX - 50;
p1.Y = _maxY;
Point cp1 = TranslateToPixels(p1);
cp1.Y = _graphTop + 20;
string stockCode = string.Format("Stock Code: {0}", _stockCode);
DrawFormattedText(stockCode, cp1, 10, Brushes.Yellow);
}
private void DrawFormattedText(string curValueS,Point pt,int fontSize, Brush foregroundBrush) {
FormattedText frmText = new FormattedText(curValueS,
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Verdana"),
fontSize,
foregroundBrush);
_drawingContext.DrawText(frmText,pt);
}
private void DrawGraph(DrawingContext drawContext)
{
_graphWidth = ActualWidth * WIDTH_FACTOR;
_graphHeight = ActualHeight * HEIGH_FACTOR;
Rect rect;
Brush fillBrush;
Pen outlinePen = new Pen(Brushes.Yellow, 1);
fillBrush = new SolidColorBrush(Colors.Black);
_graphLeft = ActualWidth * LEFT_MARGIN_FACTOR;
_graphTop = ActualHeight * TOP_MARGIN_FACTOR;
rect = new Rect(_graphLeft, _graphTop, _graphWidth, _graphHeight);
drawContext.DrawRectangle(fillBrush, outlinePen, rect);
double horGap, verGap;
horGap = _graphHeight / MAX_HOR_DIV;
verGap = _graphWidth / MAX_VER_DIV;
Pen markerPen = new Pen(Brushes.Green, 1);
for (int i = 1; i < MAX_HOR_DIV; i++)
{
Point p1 = new Point(_graphLeft, _graphTop + (i * horGap));
Point p2 = new Point(_graphLeft + _graphWidth, _graphTop + (i * horGap));
drawContext.DrawLine(markerPen, p1, p2);
}
for (int i = 1; i < MAX_VER_DIV; i++)
{
Point p1 = new Point(_graphLeft + (i * verGap), _graphTop);
Point p2 = new Point(_graphLeft + (i * verGap), _graphTop + _graphHeight);
drawContext.DrawLine(markerPen, p1, p2);
}
DrawLegend();
MapDataPoints();
Pen tracePen = new Pen(Brushes.Cyan, 1);
for (int i = 0; i < _dataPoints.Count - 1; i++)
{
drawContext.DrawLine(tracePen, _dataPoints[i], _dataPoints[i + 1]);
if (i % 10 == 0) {
Pen circlePen = new Pen(Brushes.Yellow, 1);
drawContext.DrawEllipse(Brushes.Transparent, circlePen, _dataPoints[i], 2F,2F);
}
}
}
private void UpdateGraph(StockFeed sf)
{
try
{
if (string.IsNullOrEmpty(_stockCode))
_stockCode = sf.StockCode;
if (_orgDataPoints == null)
_orgDataPoints = new List<point>();
Point p = new Point();
p.X = ++_currrentPoint;
p.Y = (double)sf.Price;
if (_orgDataPoints.Count < MAX_POINTS)
{
_orgDataPoints.Add(p);
}
else
{
for (int i = 0; i < _orgDataPoints.Count - 1; i++)
{
Point pt = _orgDataPoints[i];
pt.X = i + 1;
pt.Y = _orgDataPoints[i + 1].Y;
_orgDataPoints[i] = pt;
}
p.X = MAX_POINTS;
_orgDataPoints[MAX_POINTS - 1] = p;
}
_minX = 0;
_maxX = 250;
_minY = _orgDataPoints.Min(pt => pt.Y)*0.7;
_maxY = _orgDataPoints.Max(pt => pt.Y) * 1.25;
this.InvalidateVisual();
}
catch (Exception ex)
{
string message;
message = "Error occured while updating data points and error is" + ex.ToString();
throw new Exception(message);
}
}
private void MapDataPoints()
{
_dataPoints.Clear();
Point curPt = new Point();
Point convPt = new Point();
for (int i = 0; i < _orgDataPoints.Count; i++)
{
curPt = _orgDataPoints[i];
convPt = TranslateToPixels(curPt);
_dataPoints.Add(convPt);
}
}
private Point TranslateToPixels(Point p)
{
Point convPt = new Point();
double x, y;
x = (p.X - _minX) / (_maxX - _minX) * _graphWidth;
y =(_maxY - p.Y) / (_maxY - _minY) * _graphHeight;
if (y < 0)
y = 0;
convPt.X = _graphLeft + Math.Min(x, _graphWidth);
convPt.Y = _graphTop + Math.Min(y, _graphHeight);
return convPt;
}
public void Update(StockFeedEventArgs e)
{
if (e.StockCode.Equals(this.StockCode, StringComparison.InvariantCultureIgnoreCase))
UpdateGraph(e.SF);
}
private List<Point> _dataPoints = null;
private List<Point> _orgDataPoints = null;
private int _currrentPoint = 0;
private double _minX, _minY;
private double _maxX, _maxY;
private double _graphWidth;
private double _graphHeight;
private double _graphLeft;
private double _graphTop;
private const int MAX_POINTS = 250;
private const int MAX_HOR_DIV = 20;
private const int MAX_VER_DIV = 10;
private const double WIDTH_FACTOR = 0.85;
private const double HEIGH_FACTOR = 0.9;
private const double TOP_MARGIN_FACTOR = 0.05;
private const double LEFT_MARGIN_FACTOR = 0.075;
private DrawingContext _drawingContext = null;
private string _stockCode = string.Empty;
}
</point></point></point>
From the above code details you can see that OnRender
method is overridden to get the handle to DrawingContext
instance. DrawingContext
class exposes key methods such as DrawEllipse()
, DrawLine()
,DrawRectangle()
methods. ActualWidth
and ActualHeight
properties of the FrameworkElement
class provides the window size available for rendering. DrawGraph()
method renders the rectangular grid and also the
Trend
based on StockFeed
value. It makes use of Pen
and Brush
objects to draw the content. The StockFeed
price is displayed
on y axis and x-axis is time. TranslateToPixels()
method converts the stock price into screen coordinates before plotting the trend. StockFeed
data points
are maintained in a collection and maximum size of this collection is set as 250 points. When the collection is populated to its maximum size it then shifts the data points by one to the left.
left.Please refer UpdateGraph()
method above for more details. Update()
method is invoked whenever it gets notification of new value of a StockFeed
.
Implementation of StockDashBoard
The StockDashboard
is another type of observer used in this application. This class exposes some key properties such as Last Logged-in User
, Last Command
and Last Feed
. WPF StatusBar
control is used to display all these properties. I have used WPF data binding here and set the DataContext
property of this
control to instance of StockDashboard
class. This class is implemented as a singleton. As we are using data-binding we want automatic update of status bar whenever any of the
properties exposed by this class are changed. To achieve this we need to implement INotfiyPropertyChanged
interface. Also since this is also an observer it also implements IObserver
interface. The code details of this class are given below.
public class StatusBoard:INotifyPropertyChanged,IObserver
{
private bool _displayFeed = false;
private int _upodateCounter = 0;
private string _stockFeedMessage = string.Empty;
private string _feedMessage = string.Empty;
private static readonly StatusBoard _instance = new StatusBoard();
private StatusBoard()
{
}
public static StatusBoard CurrentDashboard
{
get { return _instance; }
}
public string LoggedInUser
{
get { return System.Environment.UserName; }
}
public string LastCommand
{
get { return _lastCommand; }
set {
_lastCommand = value;
NotifyChanged("LastCommand");
}
}
public string LastFeed
{
get { return _lastFeed; }
set
{
_lastFeed = value;
NotifyChanged("LastFeed");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
#endregion
private string _lastCommand;
private string _lastFeed;
public void Update(StockFeedEventArgs e)
{
if (!_displayFeed)
{
_stockFeedMessage += string.Format(" {0}:Rs. {1}", e.StockCode, e.SF.Price);
_upodateCounter++;
if (_upodateCounter % 4 == 0)
{
string spacers = new string(' ', _stockFeedMessage.Length);
_feedMessagge = spacers+_stockFeedMessage+spacers;
Thread newThread = new Thread(showFeed);
_displayFeed = true;
newThread.Start();
}
}
}
private void showFeed(object obj)
{
int count = _stockFeedMessage.Length * 2;
for (int i = 0; i < count; i++)
{
this.LastFeed = _feedMessagge.Substring(i, _stockFeedMessage.Length);
Thread.Sleep(300);
}
_displayFeed = false;
_stockFeedMessage = string.Empty;
}
}
Most of the above code is self explanatory. Whenever any property is changed then
NotifyChanged
method is invoked to raise the
PropertyChanged
event.
showFeed()
method is invoked in a background thread and updates the
LastFeed
as a rolling display.
Implementing StcokFeedXMLExporter
StockFeedXMLExporter
is our last type of observer in this application and it exports the StockFeed
in XML format. It groups them in a size of 250 points
and then saves it to a XML file with a timestamp. Each group of 250 StockFeed
points is saved in separate file. The implementation details of this class are given below.
public class StockFeedXMLExporter:IObserver
{
private XElement _root = new XElement("stockfeeds");
private int _noOfChilderen = 0;
private const int MAX_CHILD_ELEMENTS =250;
private string _filePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + @"\ObserverDemo\Export\";
public void Update(StockFeedEventArgs e)
{
try
{
_root.Add(new XElement("stockfeed",
new XElement("stockcode",e.SF.StockCode),
new XElement("price",e.SF.Price),
new XElement("maxprice",e.SF.MaxPrice),
new XElement("minprice",e.SF.MinPrice),
new XElement("timestamp",e.SF.StockFeedDate)
));
_noOfChilderen++;
if (_noOfChilderen >= MAX_CHILD_ELEMENTS) {
string fileName = string.Format("{0}stockfeed{1}_{2}_{3}_{4}_{5}_{6}.xml",
_filePath, DateTime.Now.Day, DateTime.Now.Month, DateTime.Now.Year, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
_root.Save(fileName);
_noOfChilderen = 0;
_root = new XElement("stockfeeds");
}
}catch(Exception ex){
string message = "Error occured while updating xml file and error is " + ex.ToString();
Logger.Log.Error(message);
}
}
}
From the above code you can see this is a very simple observer of all the three types and contains only Update()
method. This method updates the StockFeed
received as an element of stockfeeds
root element. It saves this as a XML file.
Implementation of Main Application Window
Since the application is WPF based the ObserverDemoMain
is a main window class and is derived from System.Windows.Window
class.
This GUI class acts as a main container of the application and all other GUI controls including layout panels, user controls and other GUI widgets are contained within
this main window. This class is implemeted in two files Observermain.xaml
which contains the markup for defining the layout and all gui elements and
ObserverDemo.xaml.cs
is a code behind file and acts like a controller and will contain all the handler methods to interact with GUI. This is very much
similar to .aspx
and aspx.cs
files in ASP.NET web application. This class contains the handler for all the command buttons and menu options.
In addition, this class is also responsible for initialising all the observers and maintains the list of all the observers. Since most of the observers in this application
updates GUI this class subscribes to stockPriceChanged
event of StockFeedObservable
class. It is also responsible to start and stop the trend in response
to user's invocation of these actions. Below code shows the key methods used in this class. For complete details of both the markup and full code please refer to source code provided
in the download link.
private List<IObserver> _observers = new List<IObserver>();
public Observermain()
{
InitializeComponent();
mainstatusBar.DataContext = StatusBoard.CurrentDashboard;
InitObservers();
}
private void InitObservers()
{
graphCtl.StockCode = "RELCAP";
graphCt2.StockCode = "ITC";
graphCt3.StockCode = "INFY";
graphCt4.StockCode = "TCS";
_observers.Add(graphCtl);
_observers.Add(graphCt2);
_observers.Add(graphCt3);
_observers.Add(graphCt4);
_observers.Add(StatusBoard.CurrentDashboard);
_observers.Add(new StockFeedXMLExporter());
}
private void Update(StockFeedEventArgs e)
{
if (e.SF != null)
{
_observers.ForEach(o => o.Update(e));
}
}
private void Update_StockPrice(object sender, StockFeedEventArgs e)
{
Dispatcher.Invoke((Action)(()=>Update(e)));
}
private void StartObserve_Executed(object sender, ExecutedRoutedEventArgs e)
{
StockFeedObservable.Observer.stockPriceChanged += Update_StockPrice;
StockFeedObservable.Observer.Start();
StatusBoard.CurrentDashboard.LastCommand = "Observe Trend";
}
private void StartObserve_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute=true;
}
private void StopObserve_Executed(object sender, ExecutedRoutedEventArgs e)
{
StockFeedObservable.Observer.stockPriceChanged -= Update_StockPrice;
StockFeedObservable.Observer.Stop();
StatusBoard.CurrentDashboard.LastCommand = "Stop Trend";
}
From the above code you can see that, The list of observers is initialised using InitObservers()
method. Also the datacontext property of StatusBar
control is set to instance of StatusBoard
observer. The StartObserve_Executed()
method is a handler for StartObserveCommand
and here
it subscribes to stockPriceChanged
event. As mentioned earlier since the background thread can't be used to update the GUI elements the observers have subscribed
indirectly. Update_Price()
method then delegates it to Update()
method which is invoked using Dispatcher.Invoke()
method. Also see the use
of lambda expression to invoke Update()
methods of each of the observers.
Role of Dispatcher.Invoke() Method
In WPF, only the thread that created a DispatcherObject may access that object. For example, a background thread that is spun off from the main UI thread cannot update the
contents of a GUI elements that were created on the UI thread. In order for the background thread to access this, the background thread must delegate the work to the Dispatcher
associated with the UI thread. This is accomplished by using either Invoke or BeginInvoke. Invoke is synchronous and BeginInvoke is asynchronous.
The operation is added to the event queue of the Dispatcher at the specified DispatcherPriority.
Invoke is a synchronous operation; therefore, control will not return to the calling object until after the callback returns.
Data Binding
Below markup shows the data-binding syntax used to update the StatusBar
control.
<StatusBar Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Name="mainstatusBar" HorizontalAlignment="Stretch" Background="{StaticResource statusbarBackgroundBrush}" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" >
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" VerticalAlignment="Center" Text="User: " />
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" Name="txtLoggedInUser" Text="{Binding Path=LoggedInUser}" MinWidth="150" HorizontalAlignment="Left" />
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" VerticalAlignment="Center" Text="Last Command: " />
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" Name="txtLastCommand" Text="{Binding Path=LastCommand}" MinWidth="150" HorizontalAlignment="Left" />
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" VerticalAlignment="Center" Text="Last Feed: " />
<TextBlock Style="{StaticResource headerTextBlock}" Margin="3" Name="txtLastFeed" Text="{Binding Path=LastFeed}" MinWidth="150" HorizontalAlignment="Left" />
</StackPanel>
</StatusBar>
From the above mark-up please see the data-binding syntax. The Text
property is bind to public properties of statusBoard
instance which is set as DataContext
property of StatusBar
control.
WPF Themes
We can use WPF Themes to style the controls used in WPF application. You can define new theme as XAML
file. Below markup shows sample theme for Brush
object.
The themes used in this application under app.xaml
as it's defined under it's application resources. The sample markup in app.xaml
is shown below.
Please refer the source code provided in download link for more details.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes\Brushes.xaml" />
<ResourceDictionary Source="Themes\General.xaml" />
<ResourceDictionary Source="Themes\Toolbar.xaml" />
<ResourceDictionary Source="Themes\DataGrid.Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>gt;
<!-- Generic brushes #3F5478 -->
<SolidColorBrush x:Key="DefaultControlBorderBrush" Color="#FF688CAF"/>
<SolidColorBrush x:Key="DefaultControlBackgroundBrush" Color="#FFE3F1FE"/>
<SolidColorBrush x:Key="DefaultControlForegroundBrush" Color="#FF10257F"/>
<SolidColorBrush x:Key="DefaultBorderBrush" Color="#3F5478"/>
<SolidColorBrush x:Key="DefaultBackgroundBrush" Color="#BCC7D8"/>
<SolidColorBrush x:Key="borderBackgroundBrush" Color="#3F5478"/>
<SolidColorBrush x:Key="statusbarBackgroundBrush" Color="#3F5478"/>
<!--<SolidColorBrush x:Key="ListBoxBackgroundBrush" Color="White"/>-->
<SolidColorBrush x:Key="HighLightBackgroundBrush" Color="#3F5478"/>
<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="LightGray"/>
<SolidColorBrush x:Key="HighLightBorderBrush" Color="Orange"/>
</ResourceDictonary>
Command Binding
You can use pre-defined application commands or define custom commands. These commands then can be linked to toolbar buttons or menu items using WPF data-binding. You can define
Command_Executed()
and Can_Execute()
handlers for these commands. In Can_Execute()
handler you can define logic to enable/disable command button.
Please see the code for more detail of markup and code used for defining custom commands. All the commands are defined as instance of RoutedUICommand
Implementation of Logger class
Purpose of this class is just to demonstrate the application of Singleton design pattern.You can use Logging Application Block provided by Enterprise Library available
for download on Microsoft's Patteren and Practice Website.
You can read my article Implementing
Custom Action Pane and Custom Ribbon for Excel 2010 using VSTO and C# for detailed implementation details of this class.
Download Source Code and Data Files
Please download the DownloadData.zip using the download links provided at the top of this article. Please extract the zip file in your MyDocuments
folder. So it creates
the folder structure and also copies the data files required for simulating the StockFeed
.
Folder structure under MyDocuments
folder is given below.
- ObserverDemo
- Stocks (This folder contains all data files)
- Logs (This folder keeps all the log files)
- Export (This folder keeps the XML file exported)
Points of Interest
I have created chm
help file from XML document created from the source code comments. I have used SandCastle Help File Builder
to build the help file. I have integrated the help file in this application and can be invoked from the help menu option. You can generate XML document by right clicking on solution and select the project
properties and tick the XML documentation option in output under build tab. Please see the screen shot shown in figure 3.
Figure 3: Dialog Box for setting output option for generating XML document.
I have published this application as one-click deployment. You can download the application setup using link here. Also you need to download the data files before running
this application as the data files are pre-requisite to simulate the StockFeed
.
Those who are interested in other design patterns then you use below links for more details.
Conclusion
Design patterns provide solution to recurring problems faced in software development. Observer is popular design pattern and I hope this article will provide you the insight of how this pattern
works and its application in real life scenarios. Also WPF is a very powerful framework provided by Microsoft and enables us to develop powerful GUI and is a new alternative to older win-form
based applications. I hope you will enjoy reading this article. If you have any queries and need more information then you can e-mail me. Thank you.
I am Solution Architect with 20+ years of IT experience in the field of real time,embedded,client/server and web based applications and Business Intelligence . I am currently working as Senior Consultant for Infor.