Archive

Posts Tagged ‘MEF’

BrightScript extension for Visual Studio 2012

October 2, 2013 Leave a comment

I am happy to announce that I have finally updated my extension for BrightScript.  It previously only supported Visual Studio 2010 but I have finally had enough time to make the modifications and get it working for Visual Studio 2012.

BrightScript Extension

 

You can get it here or through Visual Studio using Tools | Extensions and Updates.  Then all you need to do is simply do a search online for “BrightScript”.

For those of you who develop for the Roku or BrightSign devices, here is what your code will look like now in Visual Studio:

BrightScript

 

 

Hope this helps…

Writing a BrightScript syntax highlight extension for Visual Studio 2010

July 31, 2012 6 comments

I have recently been involved in a project where we have been required to develop using BrightScript.  BrightScript is the programming language for Roku devices.  Roku is the consumer product and there are also commercial offerings as well that allow you to build your own custom solution without ever needing to use BrightSign’s network.

Since I enjoy using Visual Studio 2010 (soon to be Visual Studio 2012)  for my development, I wanted to have a rich development experience with BrightScript.  Currently we either used NotePad++ or Visual Studio 2010 but without any syntax highlighting.  With NotePad++ it is possible to get syntax highlighting but it wasn’t the best experience.  I decided that I wanted to do some research and see just how difficult it is to write an extension to Visual Studio that would allow me to have syntax highlighting for BrightScript, specifically for “.brs” extensions.

Here is what I currently have for my development environment:

  • Visual Studio 2010 Ultimate SP1
  • Visual Studio 2010 SDK SP1

I don’t believe that you need anything else to do what I have here.  You will also need Visual Studio 2010 Professional or better to be able to do this.

Okay, so let’s get started.  First off, let’s create a project to represent our syntax.  When you launch Visual Studio, click on the Extensibility category and then select, “Editor Classifier”.  I am going to call this project “BrightScriptSyntax”.

When you click OK, Visual Studio will generate several files for you as shown below:

We will examine each of these files as well as introduce a couple others.  Let’s take a look at “BrightScriptSyntax.cs”:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows.Media;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Language.StandardClassification;
using System.Text.RegularExpressions;

namespace BrightScriptSyntax
{

#region Provider definition
/// <summary>
/// This class causes a classifier to be added to the set of classifiers. Since
/// the content type is set to "text", this classifier applies to all text files
/// </summary>
[Export(typeof(IClassifierProvider))]
[ContentType("brs")]
internal class BrightScriptProvider : IClassifierProvider
{
/// <summary>
/// Import the classification registry to be used for getting a reference
/// to the custom classification type later.
/// </summary>
[Import]
internal IClassificationTypeRegistryService ClassificationRegistry = null;

public IClassifier GetClassifier(ITextBuffer buffer)
{
return buffer.Properties.GetOrCreateSingletonProperty<BrightScript>(delegate { return new BrightScript(ClassificationRegistry); });
}
}
#endregion //provider def

#region Classifier
/// <summary>
/// Classifier that classifies all text as an instance of the OrinaryClassifierType
/// </summary>
class BrightScript : IClassifier
{
BrightScriptLanguage _brightScriptLanguage;
IClassificationType _classificationType;
IClassificationType _whiteSpaceType;
IClassificationType _keywordType;
IClassificationType _commentType;
IClassificationType _stringType;
IClassificationType _identifierType;
IClassificationType _numericType;

internal BrightScript(IClassificationTypeRegistryService registry)
{
_brightScriptLanguage = new BrightScriptLanguage();
_classificationType = registry.GetClassificationType("BrightScript");
_whiteSpaceType = registry.GetClassificationType(
              PredefinedClassificationTypeNames.WhiteSpace);
_keywordType = registry.GetClassificationType(
              PredefinedClassificationTypeNames.Keyword);
_commentType = registry.GetClassificationType(
              PredefinedClassificationTypeNames.Comment);
_stringType = registry.GetClassificationType(
              PredefinedClassificationTypeNames.String);
_identifierType = registry.GetClassificationType(
              PredefinedClassificationTypeNames.Operator);
_numericType = registry.GetClassificationType("BrightScriptNumeric");
}

/// <summary>
/// This method scans the given SnapshotSpan for potential matches for this
        /// classification.
/// In this instance, it classifies everything and returns each span as a
        /// new ClassificationSpan.
/// </summary>
/// <param name="trackingSpan">The span currently being classified</param>
/// <returns>A list of ClassificationSpans that represent spans identified
        /// to be of this classification</returns>
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span)
{
//create a list to hold the results
List<ClassificationSpan> classifications = new List<ClassificationSpan>();
string current = span.GetText();
bool commentFound = false;
// Note:  Comments should go to the end of the line.
foreach (var item in _brightScriptLanguage.Comments)
{
Regex reg = new Regex(item, RegexOptions.IgnoreCase);
var matches = reg.Matches(current);
for (int i = 0; i < matches.Count; i++)
{
commentFound = true;
Match m = matches[i];
Span new_span = new Span(span.Start.Position + m.Index,
                      current.Length - m.Index);
SnapshotSpan new_snapshot = new SnapshotSpan(span.Snapshot,
                      new_span);
var newText = new_snapshot.GetText();
classifications.Add(new ClassificationSpan(new_snapshot,
                      _commentType));
}
}
if (commentFound)
return classifications;
Classify(classifications, current, span, _brightScriptLanguage.Custom,
              _classificationType);
Classify(classifications, current, span, _brightScriptLanguage.Quoted,
              _stringType);
Classify(classifications, current, span, _brightScriptLanguage.KeyWords,
              _keywordType);
Classify(classifications, current, span, _brightScriptLanguage.IdentifierTypes,
              _identifierType);
Classify(classifications, current, span, _brightScriptLanguage.Numeric,
              _numericType);
return classifications;
}
private void Classify(List<ClassificationSpan> classifications, string current,
          SnapshotSpan span, List<string> matchList, IClassificationType classificationType)
{
foreach (var item in matchList)
{
Regex reg = new Regex(item, RegexOptions.IgnoreCase);
var matches = reg.Matches(current);
for (int i = 0; i < matches.Count; i++)
{
Match m = matches[i];
Span new_span = new Span(span.Start.Position + m.Index, m.Length);
SnapshotSpan new_snapshot = new SnapshotSpan(span.Snapshot, new_span);
var newText = new_snapshot.GetText();
classifications.Add(new ClassificationSpan(new_snapshot,
                      classificationType));
}
}
}

#pragma warning disable 67
// This event gets raised if a non-text change would affect the classification
        // in some way, for example typing /* would cause the classification to
        // change in C# without directly affecting the span.
public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
#pragma warning restore 67
}
#endregion //Classifier
}

This file differs slightly from what was originally generated by the Project Template. First, note that the ContentType is set to “brs”. This is the extension for which I want the syntax highlighting to work. I don’t want my syntax highlighter to affect all text files as is the default behavior.

The BrightScriptProvider class is has not been changed.

The next portion is where all of our work is going to be constrained from a development perspective. The BrightScript class is our classifier. It is this class that tell Visual Studio how to go about providing syntax highlighting for our BrightScript language. I want to point out that I did cheat a little and I included the following assembly to help make my development a little easier and more consistent with what Visual Studio already does with languages like C#. If you look at the screenshot below, you will see the project reference that I included:

As you can see, you will need to include the Microsoft.VisualStudio.Language.StandardClassification assembly.

It is this assembly that we get the bulk of our syntax formats. You will observe that I provide two custom formats and fall back on using the standard formats from Visual Studio.

Next, we will look at the GetClassificationSpans method. This method is the engine for performing our syntax highlighting. I keep a collection of ClassificationSpan objects that will tell Visual Studio how to classify the text it encounters. I start off by first trying to find comments in BrightScript. The rule is simple: if a comment is found, then take the rest of the current line and make it part of the comment. This is a basic single line comment rule. I have a custom class called, “BrightScriptLanguage” that represents the keywords and language of BrightScript. I loop through each keyword that is defined as a comment and create a new ClassificationSpan with the correct span information as well as indicated for it to use the comment classification type.

After I finish the loop, I then check to see if I previously found a comment. If this is true, then I return with the current list of ClassificationSpan objects. Otherwise, I then perform similar checks against Custom, Quoted (strings), Keywords, IdentifierTypes, and Numeric language types. We will look at the language definition very soon.

Finally, you will see the helper method that I wrote that is almost exactly like that of the comment check but does not assume the whole line when a match is found.

At the end of this class is a public EventHandler that is provided to us for free by the Project Template.

Let’s take a look at the “BrightScriptLanguage”:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Text.Classification;

namespace BrightScriptSyntax
{
class BrightScriptLanguage
{
#region Member Variables

private List<string> _comments = new List<string>();
private List<string> _quoted = new List<string>();
private List<string> _numeric = new List<string>();
private List<string> _keyWords = new List<string>();
private List<string> _identiferTypes = new List<string>();
private List<string> _custom = new List<string>();

#endregion

#region Properties

public List<string> Comments
{
get { return _comments; }
}
public List<string> Quoted
{
get { return _quoted; }
}
public List<string> Numeric
{
get { return _numeric; }
}
public List<string> KeyWords
{
get { return _keyWords; }
}
public List<string> IdentifierTypes
{
get { return _identiferTypes; }
}
public List<string> Custom
{
get { return _custom; }
}

#endregion

#region ctor

public BrightScriptLanguage()
{
Initialize();
}

#endregion

#region Methods

private void Initialize()
{
_custom.Add(@"\breturn\b");

_comments.Add("\'");
_comments.Add(@"\brem\b");

_quoted.Add(@"([""'])(?:\\\1|.)*?\1");

_numeric.Add(@"\b\d+\b");
_numeric.Add(@"\>");
_numeric.Add(@"\<");
_numeric.Add(@"\<\>");
_numeric.Add(@"\=");
_numeric.Add(@"\=\=");

_keyWords.Add(@"\bpos\b");
_keyWords.Add(@"\band\b");
_keyWords.Add(@"\bprint\b");
_keyWords.Add(@"\bline_num\b");
_keyWords.Add(@"\bor\b");
_keyWords.Add(@"\bgoto\b");
_keyWords.Add(@"\bnext\b");
_keyWords.Add(@"\bnot\b");
_keyWords.Add(@"\bstep\b");
_keyWords.Add(@"\bdim\b");
_keyWords.Add(@"\bthen\b");
_keyWords.Add(@"\bstop\b");
_keyWords.Add(@"\belse\b");
_keyWords.Add(@"\bto\b");
_keyWords.Add(@"\btab\b");
_keyWords.Add(@"\bobjfun\b");
_keyWords.Add(@"\btype\b");
_keyWords.Add(@"\brnd\b");
_keyWords.Add(@"\btrue\b");
_keyWords.Add(@"\bfalse\b");
_keyWords.Add(@"\bcreateobject\b");
_keyWords.Add(@"\bend while\b");
_keyWords.Add(@"\bexit while\b");
_keyWords.Add(@"\bwhile\b");
_keyWords.Add(@"\bend sub\b");
_keyWords.Add(@"\bsub\b");
_keyWords.Add(@"\bend for\b");
_keyWords.Add(@"\bfor each\b");
_keyWords.Add(@"\bfor\b");
_keyWords.Add(@"\bexit\b");
_keyWords.Add(@"\bendif\b");
_keyWords.Add(@"\bif\b");
_keyWords.Add(@"\bend function\b");
_keyWords.Add(@"\bfunction\b");
_keyWords.Add(@"\bas\b");
// Types
_identiferTypes.Add(@"\bvoid\b");
_identiferTypes.Add(@"\bboolean\b");
_identiferTypes.Add(@"\binteger\b");
_identiferTypes.Add(@"\bfloat\b");
_identiferTypes.Add(@"\bdouble\b");
_identiferTypes.Add(@"\bstring\b");
_identiferTypes.Add(@"\bobject\b");
_identiferTypes.Add(@"\binterface\b");
_identiferTypes.Add(@"\binvalid\b");
}

#endregion
     };
}

This class basically provides the meta-data necessary for me to use regular expression to determine whether or not we have a match. It is pretty much explanatory but I wanted to show you my simple regular expression solution for parsing the text.

Let’s move on to the “BrightScriptSyntaxType.cs” file:

using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace BrightScriptSyntax
{
internal static class BrightScriptClassificationDefinition
{
/// <summary>
/// Defines the "BrightScript" classification type.
/// </summary>
[Export(typeof(ClassificationTypeDefinition))]
[Name("BrightScript")]
internal static ClassificationTypeDefinition BrightScriptType = null;

[Export(typeof(ClassificationTypeDefinition))]
[Name("BrightScriptNumeric")]
internal static ClassificationTypeDefinition BrightScriptNumericType = null;

[Export]
[Name("brs")]
[BaseDefinition("text")]
internal static ContentTypeDefinition BrightScriptContentDefinition = null;

[Export]
[FileExtension(".brs")]
[ContentType("brs")]
internal static FileExtensionToContentTypeDefinition
          BrightScriptFileExtensionDefinition = null;
}
}

This class uses MEF by exporting the required information necessary for this extension. First off, we see our two custom classification types: “BrightScript” and “BrightScriptNumeric”. Next we see our entries for satisfying the file extension requirement. This is two-part in that we need a ContentTypeDefinition as well as a FileExtensionToContentTypeDefinition entry. We use the ContentTypeDefinition to define the name and base definition of our content type. Next we use the “.brs” file extension as well as reference the “brs” content type.

The final file that wee need to take a look at is the, “BrightScriptSyntaxFormat.cs” file:

using System.ComponentModel.Composition;
using System.Windows.Media;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace BrightScriptSyntax
{
#region Format definition
/// <summary>
/// Defines an editor format for the BrightScript type that has a purple background
/// and is underlined.
/// </summary>
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "BrightScript")]
[Name("BrightScript")]
[UserVisible(true)] //this should be visible to the end user
[Order(Before = Priority.Default)] //set the priority to be after the default classifiers
internal sealed class BrightScriptFormat : ClassificationFormatDefinition
{
/// <summary>
/// Defines the visual format for the "BrightScript" classification type
/// </summary>
public BrightScriptFormat()
{
this.DisplayName = "BrightScript"; //human readable version of the name
this.ForegroundColor = Colors.OrangeRed;
}
}
#endregion //Format definition

#region Format definition
/// <summary>
/// Defines an editor format for the BrightScript type that has a purple background
/// and is underlined.
/// </summary>
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "BrightScriptNumeric")]
[Name("BrightScriptNumeric")]
[UserVisible(true)] //this should be visible to the end user
[Order(Before = Priority.Default)] //set the priority to be after the default classifiers
internal sealed class BrightScriptNumericFormat : ClassificationFormatDefinition
{
/// <summary>
/// Defines the visual format for the "BrightScript" classification type
/// </summary>
public BrightScriptNumericFormat()
{
this.DisplayName = "BrightScriptNumeric"; //human readable version of the name
this.ForegroundColor = Colors.Goldenrod;
}
}
#endregion //Format definition
}

It is this file that defines the custom formats we want for “BrightScript” and “BrightScriptNumeric”. Most of these attributes are already provided by the Project Template. The only custom thing that I am performing here is setting the ForegroundColor to the corresponding value for each format.

Before we can upload this extension, we need to be sure that we have all of our information correct in the “vsixmanifest” file as shown below:

Just fill in the information that best describes your extension.  I also provided an icon and an image preview so that when developers search for my extension, they can see a sample of what it does.

Finally, I uploaded the extension with the Visual Studio Extension Gallery as shown below:

That’s all there is to it! I hope this helps anyone else who has wondered or wanted to write a custom Visual Studio Extension

Here is a link to the project.

Mastering LOB Development for Silverlight 5: A Case Study in Action

May 11, 2012 1 comment

I recently picked up a copy of the Mastering LOB Development for Silverlight 5: A Case Study in Action and started going through the book.

If you have never done any development in Silverlight and would like a practical pragmatic example of how to setup your solutions in Visual Studio as well as get exposed to the different frameworks that are available, then this is an excellent choice! The book is broken up into eleven chapters and it starts from introducing you to the basics of developing in XAML and quickly builds upon this knowledge to lead you into understanding windows and controls.

Chapter 3 is about data binding but it doesn’t stress the need for separation of concerns enough. I would have rather seen examples done the right way instead of the easy way. There were no advanced examples of attached properties or use of the System.Windows.Interactivity. The title of this book implies that we will be covering Silverlight 5 but I don’t see a single mention of debugging data bindings which is brand new to Silverlight 5 nor is there any hint of ancestor relative binding, implicit data templates, custom markup extensions, etc.

Chapter 4 is on Architecture but it too is lacking in being truly comprehensive. There is no sign of Prism 4.0 nor is there any mention to Caliburn.Micro. Both of the frameworks are very powerful and more mature than either of the frameworks mentioned in the book. Although both MVVM Light Toolkit is a good tool but MEF is more of a tool for DI/IOC and not a full fledged framework that can handle messaging like event aggregation that the other frameworks provide. Another sad aspect is that there is no mention of compartmentalizing your architecture to only download XAPs on demand. Most Silverlight frameworks support this out of the box.

Chapter 5 talks about data access. The chapter focuses on RIA Services but I find it very problematic that it does not cover any authorization or authentication. It does go into good detail about using RIA Services in conjunction with Entity Framework. However, I have spent way too much time fighting RIA Services to know that it is not appropriate for enterprise level development except for smaller line of business applications. The other common issue I have with this chapter is that it doesn’t address the common scenario of “Server Not Found“. This happens when RIA Services has an exception on the server and this will happen a lot. There are techniques to solve this and it is important for you as the developer to know this.

Chapter 6 discusses Out of Browser applications and this is for the most part has good coverage of the topic.

Chapter 7 is on Testing. This is a good chapter as well but it is unfortunate that it took us until this chapter to get a mention of DI/IOC. Dependency injection and inversion of control is just as important in your architecture and design as it is in your testing projects. I wish they would have covered some of the behavior driven development libraries that are available to you such as SpecFlow. BDD really compliments and makes your development experience so much better.

Chapter 8 is on Error Control. It covers the basics but still doesn’t address how to deal with exceptions on the server.

Chapter 9 is on Integrating with other Web Applications. I personally don’t see this as an important chapter for a book on line of business applications. I would much rather have a consistent architecture and UI instead of dealing with mashups.

Chapter 10 is on Consuming Web Services. This chapter covers the basics but doesn’t go deep as learning WCF requires a book just for itself. I did like that they cover consuming a Twitter API and processing JSON.

Chapter 11 is on Security. It is here that we see our authentication and authorization for both RIA Service and WCF. We also look at what how to make cross domain calls.

This book is a great reference for building line of business applications. Although I believe that it is missing some fundamental topics in building a line of business application and is a little weak on the coverage for Silverlight 5, it is still a very good read and you will walk away armed with good knowledge for building line of business applications.

Hope the review helps…

Building a better mouse trap…Notification Windows

July 1, 2011 2 comments

If you have played with Silverlight 4, you probably have come across the NotificationWindow. I think it is a great idea. Unfortunately, I think it is too limited for real world scenarios. The only blog post that really tries to make it something that could possibly be useful in a Silverlight asynchronous world is this one by Tim Heuer. If you are interested go ahead and read what he proposes. Back already? I think that takes us a little closer but what I am really looking for is a notification system that allows me to have multiple notifications at a time like that of Outlook or a chat program.

Here is what I am looking for, what I really want is a system like Growl or any of its kind. I want it to be able to run from either in the browser or out of th browser for Silverlight applications. Let’s fast forward and take a look at what I am proposing and the solution that I have come up with. Let me introduce you to the Notification Manager (really great on names right?):

Of course the sample application is only to demonstrate the how the notification system works in Silverlight. Let’s look at the folder structure that makes up this solution:

Don’t worry we will walk through everything that is here and get a good understanding as to what is going on before we finish. For the purpose of this blog post, I have put everything under a single project but normally I would separate this out into its own project, e.g. Core. That is why I have a folder with the name Core and all of the pertinent objects fall under it.

First let’s discuss some of the requirement I had in mind and some of the decisions I made to make this happen:

  • I wanted to have multiple notification windows visible at any time.
  • I wanted to be able to control whether the notifications would auto-hide or not.
  • I wanted to style my notification system like that of the Notification Window.
  • I wanted the notifications to come up in my applications in the bottom right side of the application.
  • I wanted the notifications to stack up from the bottom to the top of the application.
  • If the notifications went past the top of the applications, I wanted to be able to scroll through them.
  • I wanted to be able to close any notification on demand.
  • I wanted to use an API that could potentially be used in a single line of code.

This example uses Caliburn.Micro for event aggregation and MEF for composition. Feel free to use any framework or tools that you are comfortable with to modify this. I wanted to show you a functional example using Caliburn.Micro so that you could just use it as is and port it into your own projects if you wanted to.

Now that you know a little of the requirments that I had, let’s take a look and see what I ended up doing to satisfy those requirements. First off, I created two controls: NotificationControl and NotificationManager.

Let’s look at the NotificationManager first:

namespace Core.Controls
{
    using System;
    using System.Collections;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    using System.Windows.Media.Imaging;
    using Core.Models;

    public class NotificationManager : DockPanel
    {
        #region Dependency Properties

        #region Interval

        public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
            "Interval",
            typeof(int),
            typeof(NotificationManager),
            new PropertyMetadata(0));

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public int Interval
        {
            get { return (int)GetValue(NotificationManager.IntervalProperty); }
            set { SetValue(NotificationManager.IntervalProperty, value); }
        }

        #endregion

        #region ItemsSource

        public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
            "ItemsSource", typeof(IEnumerable), typeof(NotificationManager), new PropertyMetadata(OnItemsSourceChanged));
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
        private static NotificationManager _notificationManager;
        private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            _notificationManager = d as NotificationManager;
            var old = e.OldValue as INotifyCollectionChanged;
            if (old != null)
            {
                old.CollectionChanged -= items_CollectionChanged;
            }
            var items = e.NewValue as INotifyCollectionChanged;
            if (items != null)
            {
                items.CollectionChanged += new NotifyCollectionChangedEventHandler(Items_CollectionChanged);
            }
        }
        static void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                if (e.NewItems != null)
                {
                    foreach (Core.Models.NotificationModel newItem in e.NewItems)
                    {
                        var item = newItem;
                        if (item != null)
                        {
                            _notificationManager.AddNotification(item);
                        }
                    }
                }
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                if (e.OldItems != null)
                {
                    foreach (Core.Models.NotificationModel oldItem in e.OldItems)
                    {
                        var item = oldItem;
                        if (item != null)
                        {
                            _notificationManager.RemoveNotification(item);
                        }
                    }
                }
            }
        }

        #endregion

        #endregion

        #region ctor

        public NotificationManager()
        {
            this.LastChildFill = false;
        }

        #endregion

        #region Helper Methods

        public void AddNotification(NotificationModel item)
        {
            NotificationControl nc = new NotificationControl(item, this.Interval);
            this.Children.Add(nc);
            // Wire up the closed event.
            nc.Closed += (s, earg) =>
            {
                var n = s as NotificationControl;
                if (n != null)
                {
                    var child = (from Core.Models.NotificationModel c in this.ItemsSource where c.Id == n.Id select c).FirstOrDefault();
                    if (child != null)
                    {
                        var coll = this.ItemsSource as Caliburn.Micro.BindableCollection;
                        if (coll != null)
                        {
                            // Remove from the data collection.
                            coll.Remove(child);
                        }
                    }
                }
            };
        }
        public void RemoveNotification(Core.Models.NotificationModel item)
        {
            var child = (from NotificationControl c in this.Children where c.Id == item.Id select c).FirstOrDefault();
            if (child != null)
            {
                this.Children.Remove(child);
            }
        }

        #endregion

    };
}

As you can see, there is not a lot of logic to this control. This control derives from a DockPanel. I need the DockPanel so that I don’t have to spend a lot of time computing the layout of the NotificationControl objects. I can simply use the DockPanel and set the Dock property on each object to “Bottom” and it is ready to perform bottom up style stacking.

Next, you will see that I have two dependency properties. I have an integer Interval property that lets me determine how long the NotificationControl is visible. The default value of this property is zero (0). I use this default value as a kill switch and never hide the controls.

I also have an ItemsSource property. I wanted to have this since I wanted to be able to have the notifications added to the manager like that of a ListBox or ItemsControl. I assign a callback so that whenever the ItemsSource property is changed it get fired. I test to see if the new or old ItemsSource values are of type INotifyCollectionChanged. If so, I wire up or down the CollectionChanged event. In the CollectionChanged event handler, I examine the Action property off of the NotifyCollectionChangedEventArgs passed into the handler. I then determine if the operation is an Add or Remove. In each condition I am filtering the items in the collection to the type of a NotificationModel. This model holds all the data that will later be bound for the visual part of the NotificationControl.

We are now at the helper methods and I have one for each operation. The AddNotification method takes in a NotificationModel object as a parameter. It creates a NotificationControl object passing into the constructor the NotificationModel object and the Interval property value. Next it adds the NotificationControl object to the Children property and thus onto the DockPanel. We also wire up the Closed event on the NotificationControl and remove it from the ItemsSource collection.

In the RemoveNotification method, we also take in a NotificationModel object as a parameter. Every NotificationControl has a Guid Id property exposed and we use this Id value to remove it from the Children visual collection.

Okay, we have a good understanding of the manager, let’s take a look at the NotificationControl:

namespace Core.Controls
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Threading;
    using Core.Models;
    using System.Windows.Shapes;
    using Core.Utilities;

    public class NotificationControl : ContentControl
    {
        DispatcherTimer _timer = new DispatcherTimer();

        #region ctor

        public NotificationControl(NotificationModel item, int interval)
        {
            this.DefaultStyleKey = typeof(NotificationControl);
            this._id = item.Id;
            this.Interval = interval;

            if (Interval > 0)
            {
                _timer.Interval = new TimeSpan(0, 0, 0, 0, Interval); // 5 Seconds
                _timer.Tick += new EventHandler(Each_Tick);
                _timer.Start();
            }

            // Set the Header binding.
            Binding binding = new Binding();
            binding.Source = item;
            binding.Path = new PropertyPath("Header");
            this.SetBinding(NotificationControl.HeaderProperty, binding);
            // Set the Text binding.
            binding = new Binding();
            binding.Source = item;
            binding.Path = new PropertyPath("Text");
            this.SetBinding(NotificationControl.TextProperty, binding);
            // Set the Dock property.
            DockPanel.SetDock(this, Dock.Bottom);
            // Add a little margin to the bottom and top.
            this.Margin = new Thickness(0, 2, 0, 2);
            // Swap out the icon based on the NotificationType.
            switch (item.NotificationType)
            {
                case NotificaitonType.Info:
                    this.FillBrush = new SolidColorBrush(ColorHelper.ToColor("#FF98CFE8"));
                    this.Icon = new BitmapImage(new Uri("/SilverlightApplication2;component/Assets/Images/48x48/about.png", UriKind.Relative));
                    break;
                case NotificaitonType.Question:
                    this.FillBrush = new SolidColorBrush(ColorHelper.ToColor("#FF2C3CF5"));
                    this.Icon = new BitmapImage(new Uri("/SilverlightApplication2;component/Assets/Images/48x48/help2.png", UriKind.Relative));
                    break;
                case NotificaitonType.Warning:
                    this.FillBrush = new SolidColorBrush(ColorHelper.ToColor("Yellow"));
                    this.Icon = new BitmapImage(new Uri("/SilverlightApplication2;component/Assets/Images/48x48/warning.png", UriKind.Relative));
                    break;
                case NotificaitonType.Error:
                    this.FillBrush = new SolidColorBrush(ColorHelper.ToColor("Red"));
                    this.Icon = new BitmapImage(new Uri("/SilverlightApplication2;component/Assets/Images/48x48/error.png", UriKind.Relative));
                    break;
                case NotificaitonType.Custom:
                    this.FillBrush = new SolidColorBrush(ColorHelper.ToColor("White"));
                    this.Icon = new BitmapImage(new Uri("/SilverlightApplication2;component/Assets/Images/48x48/user1.png", UriKind.Relative));
                    break;
            }
        }

        #endregion

        private Guid _id = Guid.NewGuid();
        public Guid Id
        {
            get { return _id; }
        }

        #region Dependency Properties

        #region FillBrush

        public static readonly DependencyProperty FillBrushProperty = DependencyProperty.Register(
            "FillBrush",
            typeof(Brush),
            typeof(NotificationControl),
            null);

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public Brush FillBrush
        {
            get { return (Brush)GetValue(NotificationControl.FillBrushProperty); }
            set { SetValue(NotificationControl.FillBrushProperty, value); }
        }

        #endregion

        #region Interval

        public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
            "Interval",
            typeof(int),
            typeof(NotificationControl),
            new PropertyMetadata(0));

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public int Interval
        {
            get { return (int)GetValue(NotificationControl.IntervalProperty); }
            set { SetValue(NotificationControl.IntervalProperty, value); }
        }

        #endregion

        #region Icon
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
            "Icon",
            typeof(ImageSource),
            typeof(NotificationControl),
            new PropertyMetadata(new BitmapImage(new Uri("/Core;component/Assets/Images/48x48/about.png",UriKind.Relative))));

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public ImageSource Icon
        {
            get { return (ImageSource)GetValue(NotificationControl.IconProperty); }
            set { SetValue(NotificationControl.IconProperty, value); }
        }
        #endregion

        #region Header
        public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
            "Header",
            typeof(string),
            typeof(NotificationControl),
            new PropertyMetadata(OnHeaderPropertyChanged));

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public string Header
        {
            get { return (string)GetValue(NotificationControl.HeaderProperty); }
            set { SetValue(NotificationControl.HeaderProperty, value); }
        }

        private static void OnHeaderPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        #region Text
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text",
            typeof(string),
            typeof(NotificationControl),
            new PropertyMetadata(OnTextPropertyChanged));

        ///
<summary> /// Gets or sets a value that indicates whether
 /// the field is required.
 /// </summary>
        public string Text
        {
            get { return (string)GetValue(NotificationControl.TextProperty); }
            set { SetValue(NotificationControl.TextProperty, value); }
        }

        private static void OnTextPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        #endregion

        #region Overrides

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            Button closeButton = GetTemplateChild("closeButton") as Button;
            if (closeButton != null)
            {
                closeButton.Click += new RoutedEventHandler(closeButton_Click);
            }
        }

        #endregion

        #region Events

        public event EventHandler Closed;

        void closeButton_Click(object sender, RoutedEventArgs e)
        {
            EventHandler handler = this.Closed;
            if (handler != null)
            {
                _timer.Stop();
                handler(this, EventArgs.Empty);
            }
        }

        public void Each_Tick(object o, EventArgs sender)
        {
            closeButton_Click(this, new RoutedEventArgs());
        }

        #endregion

    };
}

This is longer but really doesn’t do anything really exciting. If we look we see that the bulk of the work is done in the constructor. We also have a DispatcherTimer object and several dependency properties that represent the User Interface of the control. This control derives from the ContentControl.

In the constructor, we set the DispatcherTimer only if the Interval value passed is is greater than zero (0). Next we set the binding for Header and Text. We set the Dock property to “Bottom”. We give ourselves a slight margin on the Top and Bottom of the control so that the are not stacked on top of eachother. Finally we switch on the NotificationType and set the corresponding FillBrush and Icon properties. The FillBrush uses a ColorHelper class to makes things easier in Silverlight. In the OnApplyTemplate we find the closeButton and wire up the Click event. We also have a Closed event that gets fired whenever the button is clicked or the DispatcherTimer fires.

As you have already seen, we have an enum NotificationType:

namespace Core
{
    public enum NotificaitonType
    {
        Info,
        Question,
        Warning,
        Error,
        Custom
    };
}

We are going to skip over the Events folder and look at our Interfaces:

namespace Core
{
    using System;

    public interface INotification
    {
        string Header { get; set; }
        string Text { get; set; }
        NotificaitonType NotificationType { get; set; }
    };

    public interface INotificationService
    {
        void ProcessInfo(string header, string text);
        void ProcessQuestion(string header, string text);
        void ProcessWarning(string header, string text);
        void ProcessError(string header, Exception ex);
        void ProcessCustom(string header, string text);
    };
}

There are two interfaces: INotification and INotificationService. I use INotification for my data objects. I use INotificationService for my external API in using this solution.

Let’s look at the NotificationModel class:

namespace Core.Models
{
    using Caliburn.Micro;
    using System;

    public class NotificationModel : PropertyChangedBase, INotification
    {
        #region Properties

        private Guid _id = Guid.NewGuid();
        public Guid Id
        {
            get { return _id; }
        }

        private string _header = "";
        public string Header
        {
            get { return _header; }
            set
            {
                _header = value;
                NotifyOfPropertyChange(() => Header);
            }
        }

        private string _text = "";
        public string Text
        {
            get { return _text; }
            set
            {
                _text = value;
                NotifyOfPropertyChange(() => Text);
            }
        }

        private NotificaitonType _notificationType = NotificaitonType.Info;
        public NotificaitonType NotificationType
        {
            get { return _notificationType; }
            set
            {
                _notificationType = value;
                NotifyOfPropertyChange(() => NotificationType);
            }
        }

        #endregion

    };
}

Again, this class implements the INotification interface. It also derives from the PropertyChangedBase from Caliburn.Micro.

The next and more interesting class is the NotificationService class:

namespace Core.Services
{
    using System;
    using System.ComponentModel.Composition;
    using Core.Models;
    using Caliburn.Micro;

    [Export(typeof(INotificationService))]
    public class NotificationService : INotificationService
    {
        #region ctor

        public NotificationService()
        {

        }

        #endregion

        #region MessageBus

        private IEventAggregator _messageBus;
        [Import]
        public IEventAggregator MessageBus
        {
            get { return _messageBus; }
            set
            {
                _messageBus = value;
                if (_messageBus != null)
                {
                    _messageBus.Subscribe(this);
                }
            }
        }

        #endregion

        public void ProcessInfo(string header, string text)
        {
            Process(header, text, null, NotificaitonType.Info);
        }
        public void ProcessQuestion(string header, string text)
        {
            Process(header, text, null, NotificaitonType.Question);
        }
        public void ProcessWarning(string header, string text)
        {
            Process(header, text, null, NotificaitonType.Warning);
        }
        public void ProcessError(string header, Exception ex)
        {
            Process(header, null, ex, NotificaitonType.Error);
        }
        public void ProcessCustom(string header, string text)
        {
            Process(header, text, null, NotificaitonType.Custom);
        }
        private void Process(string header, string text, Exception ex, NotificaitonType notificationType)
        {
            NotificationModel n = new NotificationModel()
            {
                Header = header,
                Text = (notificationType == NotificaitonType.Error) ? ex.Message : text,
                NotificationType = notificationType
            };
            MessageBus.Publish(new NotificationEvent() { Notification = n });
        }
    };
}

As you can see, I am using MEF to export this type. I am also importing an IEventAggregator object that I call MessageBus. The rest is just the implementation of the INotificationService interface. The final method is the one that actually does all the work and it simply creates a new NotificationModel object and then publishes using a NotificationEvent with the Notification property set to the newly created object.

We can now take a look at the Events folder:

namespace Core
{
    using Core.Models;

    public class NotificationEvent { public NotificationModel Notification { get; set; } }
}

If you have done any event aggregation this may seem foreign but we are basically passing stronly typed messages and the definition of our message is the NotificationEvent class. It has only one property and that is of type NotificationModel.

The only other piece that is missing from the standpoint of creating these controls is the generic.xaml file and I will let you take a look at the styling when you download the project.

Let’s switch gears and look at what it takes to use this in an application. I am going not going to go into getting your project setup for Caliburn.Micro as the project site has many examples for you to download and see how all that works. We will focus on the ShellView and ShellViewModel classes.

The ShellView is shown below:

<UserControl x:Class="SilverlightApplication2.ShellView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:l="clr-namespace:Core.Controls"
             >
    <Grid x:Name="LayoutRoot" 
          Background="Black">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        
        <StackPanel VerticalAlignment="Bottom" Margin="10">
            <Button x:Name="SendInfoMessage"
                    Content="Send Info Message" 
                    Margin="5"
            />
            <Button x:Name="SendQuestionMessage"
                    Content="Send Question Message" 
                    Margin="5"
            />
            <Button x:Name="SendWarningMessage"
                    Content="Send Warning Message" 
                    Margin="5"
            />
            <Button x:Name="SendErrorMessage"
                    Content="Send Error Message" 
                    Margin="5"
            />
            <Button x:Name="SendCustomMessage"
                    Content="Send Custom Message" 
                    Margin="5"
            />

        </StackPanel>


        <ScrollViewer Grid.Column="1" 
                      Width="430" 
                      HorizontalAlignment="Right" 
                      BorderThickness="0"
                      VerticalScrollBarVisibility="Auto">
            <l:NotificationManager x:Name="Notifications" 
                                   Interval="5000"
                                   ItemsSource="{Binding NotificationItems}"/>
        </ScrollViewer>

    </Grid>
</UserControl>

Because I am using Caliburn.Micro, the buttons automatically wire up the click events. I have a ScrollViewer and then my NotificationManager. You can see that I am setting the Interval for all controls to 5000. I am also binding the ItemsSource to a NotificationItems property.

Let’s see what is happening on the ShellViewModel:

namespace SilverlightApplication2 {
    using System;
    using System.ComponentModel.Composition;
    using Core;
    using Core.Models;
    using Caliburn.Micro;

    [Export(typeof(IShell))]
    public class ShellViewModel : IShell, IHandle
    {

        #region ctor

        public ShellViewModel()
        {
            _notificationItems = new BindableCollection();
        }
        #endregion

        #region Properties

        readonly BindableCollection _notificationItems;
        public BindableCollection NotificationItems
        {
            get { return _notificationItems; }
        }

        #endregion

        #region MessageBus

        private IEventAggregator _messageBus;
        [Import]
        public IEventAggregator MessageBus
        {
            get { return _messageBus; }
            set
            {
                _messageBus = value;
                if (_messageBus != null)
                {
                    _messageBus.Subscribe(this);
                }
            }
        }

        #endregion

        #region NotificationService

        private INotificationService _notificationService;
        [Import]
        public INotificationService NotificationService
        {
            get { return _notificationService; }
            set { _notificationService = value; }
        }

        #endregion

        #region Actions

        public void SendInfoMessage()
        {
            NotificationService.ProcessInfo("Save", "Save successful!");
        }

        public void SendQuestionMessage()
        {
            NotificationService.ProcessQuestion("Confirm Delete?", "Are you sure you want to delete this record?");
        }

        public void SendWarningMessage()
        {
            NotificationService.ProcessWarning("Warning", "Organization name does not allow null.");
        }

        public void SendErrorMessage()
        {
            Exception ex = new Exception("Connection failed to database.  Please contact your system administrator.");
            NotificationService.ProcessError("Error", ex);
        }

        public void SendCustomMessage()
        {
            NotificationService.ProcessCustom("Matt says...", "Hey, what are you doing?");
        }

        #endregion

        #region IHandle

        public void Handle(NotificationEvent message)
        {
            NotificationItems.Add(message.Notification);
        }

        #endregion

    };
}

I am satisfying the requirements for MEF and exporting this class using the IShell marker interface. I am also using Caliburn.Micro’s way of event aggregation with the IHandleinterface. I have a collection of NotificationItems of type NotificationModel and I instantiate them in the constructor. I have a MessageBus property and NotficaitonService property that I bring in via MEF of type IEventAggregator and INotificationService. As you can see, each button click event method has a single line of code except for the one that create an Exception object. If you recall, the NotificationService had a Process method that actually created a NotificationModel object and then used the event aggregation to broadcast the event.

It is interesting to point that I am in essence broadcasting and handling the event in the ShellViewModel. I broadcast it throught my NotificationService object and I handle the event in the Handle method which simple add the NotificationModel object to the collection.

I hope this gives you an idea as to what you can do to provide for a very rich notification service in your applications. This approach would also work in WPF as well.

I have a few things left that I would like to do to make this a first class citizen.

  • I would like to have a simple hyperlink that allow me to copy any notification message to the clipboard.
  • I would also like to extend the Question notification so that it would actually let you do what it needs to do and that is answer yes/no/cancel or ok/cancel, etc.
  • In the Custom notification, I would like to extend it so that you could pass in your own Avatar to better represent a chat application with a hyperlink to respond.
  • Have the ability to only show a NotificationControl as a dialog and not part of the NotificationManager. Meaning that it would show up in the center of the application.

I will try and do an update blog once I have some time to further flesh this out.

I hope this helps you out and look forward to any other paradigms that you have come up with from a standpoint of notifications. I know this can be a pain if you are using WCF RIA Services in Silverlight and you have a centralized dialog that displays messages from the DomainDataSource objects. It will put popup on top of popup using the ChildWindow and that is not the best user experience. That is one of the reasons that I started looking for an alternative solution.

You can download the sample project here.

Code Camp 2010 Slides and Sample code uploaded

I have finally had some time to upload the slides and sample code for my sessions of Code Camp 2010.

Hope you enjoy.

Categories: English Tags: , , ,

Silverlight 4 Business Applications

February 27, 2010 Leave a comment

Over then next months, I will be posting about putting together an architecture for line of business applications.  First, I would like to break down the many components that would comprise building enterprise level applications.

Let’s first talk about cross-cutting concerns at a very high level:

  • Security – this would include authentication and authorization.  We will discuss permissions based authorization in order to support more than just screen level authorization but break it down to screen part and individual control level security.
    • Logging – this will provide global logging in a centralized manner.  Our goal will be to facilitate a flexible logging infrastructure without impacting any of modules that desire to log.  We should be able to change out the logging implementation whether it be a flat file, database, or rest service.
  • Exception Handling – this will provide a common means for receiving exceptions and notifying users of issues.  We will provide a mechanism to report exceptions and then allow for the developer to decide whether the end user should see the exception or just silently log to through the logging service.
  • Messaging – this service will be used throughout the other cross-cutting concerns.  It main purpose is to allow other modules to broadcast messages(events) without the need to have references to the other various modules.

We will go into each of these global services in more detail as dedicated posts.  Next we will take a look at one approach as to building a shell and module infrastructure to support the development of modular applications.  These posts will go into detail using both Prism and MEF (Managed Extensible Framework).

Stay tuned for more.

Follow

Get every new post delivered to your Inbox.

Join 209 other followers