Using M and the Oslo SDK to Build a WPF Animation DSL

When talking to colleagues about Microsoft "Oslo" M and MGrammar in particular, I'm often asked whether there is a practical problem Oslo solves and how a developer plugs Oslo into their application. In response, I always point out the samples, blogs, presentations, and documentation.

I find though, that colleagues become a bit overwhelmed with the new ideas and wealth of information. Mostly, they're looking for a single starting point-something akin to a simple sample solving some practical problem and a detailed explanation of how it's assembled. Using a sample WPF application, I'm going to provide this starting point so you can start thinking about how Oslo M could solve your business problems.

Olso Overview

If you've been following my articles, Oslo's goals and technologies should be familiar to you. If not, here is a summary.

Oslo is composed of the following components displayed in Figure 1.


Figure 1 Oslo Architecture (Source: "Microsoft PDC 2008 - A Lap Around Olso")

  • "M", a language for composing models and DSLs
  • The Repository a SQL Server database designed for storing models
  • Quadrant, a tool for editing and viewing model data

Currently, Quadrant is only available to PDC 2008 attendees. M and the Repository come with the Oslo SDK available on the Oslo Developer Center site http://msdn.microsoft.com/en-us/oslo/default.aspx.

Oslo's goal is to deliver a foundation for building and storing models of all types. Models are application metadata formatted for runtime consumption. Separate Microsoft initiatives aim to build runtimes and tooling into applications like Visual Studio that are Oslo model aware.

The M Language is composed of MSchema, MGraph, and MGrammar. A complete introduction to M is beyond the scope of this article. I introduced MSchema, MGraph, and MGrammar in my prior articles, you can read them here.

There is an underlying set of .NET Framework classes supporting all the functionality above. In this article, I'll employ some of these classes in a sample application.

Sample Application

There are two parts to the sample application: a WPF EllipseGeometry path animation and a Domain Specific Language (DSL) written in MGrammar for controlling the animation. The sample application is built on top of the January 2009 Oslo SDK CTP. A screen shot of the sample application appears in Figure 2 below.

Something you may have noticed in the Oslo introduction above is that Oslo provides the infrastructure, but the Runtime consuming Oslo is the solution. Essentially the sample application is a runtime for a DSL. So a likely question is why did I employ a DSL?

Why the DSL

A DSL is commonly defined as a sort of programming language performing a specific purpose and possessing limited language capabilities. So according to the definition some languages defined as DSLs are: RegularExpressions, TSQL, and configuration files. At first a DSL may appear to be an odd choice for controlling animation. When considering the requirements below, a DSL seemed a perfect fit.

I needed to store key X, Y coordinate positions along the animation path, so I needed some representation I could easily read and write from a file. I didn't want to store precise coordinate positions. I wanted positions to scale according to the dimensions of the user's monitor and the application window. So user's importing the animation in an application with a larger screen or application window would view the animation scaled in proper proportions.

I wanted to build something simple for a user to compose. First, I didn't want the user to think about where, for example, coordinate (30, 100) fell on the screen. I wanted the user to think more about the direction of the animation and how far the ellipse should travel before switching directions. Second, I ruled out something like XML, because I wanted the commands to look and feel like natural language.

With an understanding of the application and the motivation behind the application features, I'm going to demonstrate how the sample works, starting with an overview of the major classes.

Using M and the Oslo SDK to Build a WPF Animation DSL

Core Runtime

The following code lives behind the application's "Run" button.

MGraphGenerator generator = new MGraphGenerator();
MParserBuilder builder = new MParserBuilder();
EllipseAnimator animate = new EllipseAnimator();
MGraphReader reader = new MGraphReader();
List<Move> list = new List ();
builder.Build(_mgName);
generator.Run(textBox1.Text, builder.Parser);
reader.ReadGraphInto(generator, list);
animate.CurrentEllipse = this._ellipse;
animate.DoAnimation(this, list);

Appearing above are the major application classes. Class roles are described below.

  • MParserBuilder loads the MGrammar code and builds the Olso MGrammar parsing classes.
  • MGraphGenerator utilizes Oslo parser classes built in MParserBuilder to generate data from the DSL code.
  • MGraphReader reads the DSL generated data and stores the data in a List of Move objects.
  • EllipseAnimator performs the animation based on the List of Move objects produced by the MGraphReader.

With some orientation to the major classes, I'm going to cover each of the classes in more detail starting with the EllipseAnimator and WPF Animation.

WPF Animation

A complete introduction to WPF animation is beyond the scope of this article, so I'm only highlighting the components and ideas employed by the sample. The main animation function contained in the EllipseAnimator class appears below.

PathGeometry animationPath = null;
animationPath = GenerateAnimationPath(pointList);
// Create a PointAnimationgUsingPath to move
// the EllipseGeometry along the animation path.
PointAnimationUsingPath centerPointAnimation =
    new PointAnimationUsingPath();
centerPointAnimation.PathGeometry = animationPath;
centerPointAnimation.Duration = TimeSpan.FromSeconds(5);
centerPointAnimation.RepeatBehavior = RepeatBehavior.Forever;
Start(win, centerPointAnimation);

EllipseAnimator works with the XAML on the Window1 class. The XAML portion of the animation appears below.

<Path Fill="Blue" Margin="15,15,15,15" x:Name="myPath">
    <Path.Data>
        <!-- Describes an ellipse. -->
        <EllipseGeometry x:Name="_ellipse" Center="0,0" RadiusX="15"
             RadiusY="15" />
    </Path.Data>
</Path>

There are three parts to the animation.

  • Objects to animate plus supporting information about the objects, like the course of the animation. This is encapsulated in the PathGeometry object in the C# code and the Path object in the XAML snippet.
  • The style of animation being employed is encapsulated by the PointAnimationUsingPath class.
  • Animation activation, encapsulated in the Storyboard object.

If you're interested in WPF animation, there are some good sources at the end of this article.

PointAnimationUsingPath requires X,Y coordinates collection. As I said earlier the MGrammar DSL is charged with collecting the coordinate inputs.

MGrammar Animation DSL

The DSL MGrammar code WPFTestingWithMG appears below.

module WPFTestingWithMG
{
    language MGrammarInterpreter
    {
        interleave Whitespace = '\t' | '\n' | '\r' | ' ';
    
        syntax Main = (MoveUp | MoveDown | MoveRight )+;
        
        token Up = "Up";
        token Down = "Down";
        token Right = "Right";
        token NumVal = "0".."9";
        token Magnitude = NumVal+;
        
        
        syntax MoveUp = Up Mag:Magnitude => Move  { MoveX {0}, MoveY {Mag}, Mult{-1} };
        syntax MoveDown = Down Mag:Magnitude => Move  { MoveX {0}, MoveY {Mag}, Mult{1} };
        syntax MoveRight = Right Mag:Magnitude => Move  { MoveX {Mag}, MoveY {0}, Mult{1} };
    }
}

When WPFTestingWithMG executes it produces MGraph output appearing like the MGraph sample below.

Main[
  [
    [
      Move{
        MoveX{
          0
        },
        MoveY{
          "10"
        },
        Mult{
          1
        }
      }
    ],

WPFTestingWithMG translates the parsed command to X, Y movement along with a multiplier indicating upward movement or downward movement. The sample application reads the resulting MGraph and translates the movement to specific X, Y coordinates along the path. Later in the article I'll show how the application works with the resulting MGraph.

I built the MGrammar DSL in Intelipad and added it to the project. There were a number of steps I needed to do to get the MGrammar to work with text in the sample application.

Using M and the Oslo SDK to Build a WPF Animation DSL

Bringing MGrammar into the Application

Rather than building code to open the MGrammar file to read the code, I chose to add the MGrammar file to my Visual Studio project and make my MGrammar code an embedded resource. The embedded resource property is highlighted in Figure 3 below.


Figure 3 WPFTestingWithMG properties

A DynamicParser object must be constructed from the Resource. DynamicParser is a new class shipping with the Oslo SDK. DynamicParser is in charge of generating the MGraph. MParserBuilder constructs the DynamicParser from the WPFTestingWithMG resource. MParserBuilder's Build function appears below.

public void Build(string resourceName)
{
    
   var assembly = Assembly.GetExecutingAssembly();
   using (var stream = assembly.GetManifestResourceStream(resourceName))
   using (var reader = new StreamReader(stream))
   {
      var errorReporter = ErrorReporter.Standard;
      var compiler = new MGrammarCompiler
      {
         SourceItems = new SourceItem[] { 
            new SourceItem { 
                 Name = "MGrammarInterpreter Grammar", 
                 ContentType = ContentType.Mg, 
                 TextReader = reader,
            },
         },
      };
      if (compiler.Compile(errorReporter) != 0 || errorReporter.HasErrors)
      {
         throw new Exception("Failure compiling WPFTestingWithMG.mg");
      }
      compiler.LoadDynamicParser(this.Parser);
   }
}

Build reads the code from the embedded resource, compiles the MGrammar code, and associates the complied code with the DynamicParser.

The next task was to programmatically generate MGraph from the DynamicParser.

Generating MGraph

MGraphGenerator feeds the DSL code to the DynamicParser object to generate the MGraph. MGraphGenerator Run function appears below.

public void Run(string inputText,DynamicParser parser)
{
   ExceptionErrorReporter errorReporter;
   parser.GraphBuilder = _builder;
   errorReporter = new ExceptionErrorReporter();
   GraphRoot = parser.Parse<object>(string.Empty, 
           new StringReader(inputText), errorReporter);
   if (GraphRoot == null || errorReporter.HasErrors)
   {
   }
   else
   {
   }
}

GraphBuilder contains the data resulting from the parsed DSL code. GraphBuilder is a collection of object classes. GraphBuilder includes a set of functions for adding and traversing the object collection. GraphBuilder is hierarchal with a root and multiple child elements. GraphBuilder is a little clunky to use. I'm hoping future versions of the Oslo SDK will include a richer MGraph consumption experience or more guidance on constructing your own GraphBuider.

You may have noticed that I bypassed building MSchema. In its current incarnation, MSchema is associated more with SQL Server and the Oslo Repository. Since I'm not utilizing the Oslo Repository, I did nothing with MSchema.

Reading and consuming GraphBuilder is the job of the MGraphReader class.

Consuming MGraph

MGraphReader pulls the data from a GraphBuilder. A snippet from the MGraphReader WalkTheNodes function appears below.

private void WalkTheNodes(System.Dataflow.GraphBuilder graph, 
       object nodeOn, List<Move> list)
{
    List<object> vals;
    string MoveX = "";
    string MoveY = "";
    string seqLabel = "";
    object seqLabelObj = null;
    List<object> tempPos;
    Move moveObj = null;
    bool isNode = false;
    double moveOffset = 3.0;
    double moveMultiplier;
    
    foreach (object enumNext in graph.GetSuccessors(nodeOn))
    {
       isNode = graph.IsNode(enumNext);
       if ((isNode)) //Skip the non-nodes
       {
          seqLabelObj = graph.GetSequenceLabel(enumNext);
          if (seqLabelObj != null) //Sometimes label returns null value
          {
             seqLabel = seqLabelObj.ToString();
             if (seqLabel == @"Move") //You reach a Move label
             {
                 //Successors of Move are MoveX, MoveY, and Mult
                 vals = graph.GetSuccessors(enumNext).ToList<object>();
                 //ToList converts IEnumerable to List generic
                 //MoveX Successor is the MoveX value
                 tempPos = graph.GetSuccessors(vals[0]).ToList<object>();
                 MoveX = tempPos[0].ToString();
                 list.Add(moveObj);
             }
          }
          WalkTheNodes(graph, enumNext, list);
       }
    }
}

WalkTheNodes recursively visits the nodes on GraphBuilder, building a list of Move objects required by the EllipseAnimator class discussed earlier in the article. The Nodes labeled "Move" and successors below the "Move" nodes contain the runtime's data.

GraphBuilder functions getting node objects return objects based on the IEnumerble interface. To move GraphBuilder data into List collections, MGraphReader employs .NET classes working with IEnumerable interfaces.

Conclusion

This sample application shows how M and the Olso SDK can be incorporated into a Visual Studio application. Using MGrammar I built a DSL to configure an animation. I also built a WPF animation runtime for consuming data emitted by the MGrammar DSL.

About the Author

Jeffrey Juday is a software developer specializing in enterprise integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra. You can reach Jeff at me@jeffjuday.com.

Sources

Martin Fowler's DSL work

Oslo SDK documentation

WPF Animation Documentation on MSDN

Oslo development center Samples and Documentation



About the Author

Jeffrey Juday

Jeff is a software developer specializing in enterprise application integration solutions utilizing BizTalk, SharePoint, WCF, WF, and SQL Server. Jeff has been developing software with Microsoft tools for more than 15 years in a variety of industries including: military, manufacturing, financial services, management consulting, and computer security. Jeff is a Microsoft BizTalk MVP. Jeff spends his spare time with his wife Sherrill and daughter Alexandra.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds