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.
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.
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.
Source Code
The source code for this article: MGrammarInterpreter.zip.
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