Welcome to WindowsClient.net | Sign in | Join

Rob Relyea - XAMLified

WPF, Silverlight and XAML

June 2009 - Posts

Earlier this year I wrote about my 20-40 program with Sammamish Rowing Association

I had a great 4 months getting back into rowing. This weekend I rowed in my first Masters Regional Regatta in Vancouver, WA.  It was a good end to my first season back rowing. I rowed in two quad races (advancing out of the heats to the finals in 1) and one double race (we missed qualifying for the finals by less than 2 seconds.).

I'm going to continue to row year round, as I'm really enjoying being back in the boat, and I'm looking foward to what a full year of training and practice can do to my rowing/racing. I'm going to try to increase the number of workouts that I can fit in outside of rowing days, so that I can give it 100%.

This isn't the same thing as a Rob Day (which I've been marginally successful taking every month), but serves the same purpose.

(Thanks to my wife, who tolerates me waking up insanely early 3 days a week.)

Posted by Rob_Relyea | with no comments
Filed under:

[Beta 1 of .NET 4 has released, and we’ve been doing a series of blog posts describing new XAML functionality.]

One of the things that most excites me about System.Xaml.dll is the flexibility that our programming model gives to people who need something extra from XAML.  You may already know that we've componentized XamlReader.Load() into a component that reads XML and produces XAML nodes (XamlXmlReader) and a component which takes XAML nodes and creates an object graph (XamlObjectWriter).

These readers and writers have base classes (System.Xaml.XamlReader and System.Xaml.XamlWriter) which define a model of XamlReaders which produce XAML nodes and XamlWriters which consume XAML nodes and do something interesting.  The model is similar to XmlReader and XmlWriter from System.Xml.dll.

So what is a XAML node?

We have 7 XamlNodeTypes:

  • StartObject - Signifies the start of an object instance in XAML.  This is an object element start tag or a markup extension (inside an attribute value).
  • EndObject - Signifies the end of an object instance in XAML.  This is an object element end tag or a markup extension close (inside an attribute value).
  • StartMember - Signifies the start of a member (property, event, attached property, attached event, or directive). These are represented in markup as attributes or property elements.
  • EndMember - Signifies the end of a member.
  • Value - Attribute values or text nodes in the XamlXmlReader's case.
  • Namespace - Signifies the definition of a namespace and prefix pair.  These come before the StartObject that they are specified on in XAML's XML syntax.
  • GetObject - Signifies the start of an object instance in XAML.  This occurs for r/o collections or r/w collections without an explicit collection element.

XamlReader and these nodes define the Data Model for XAML - which I like to call “Objects - Members - Values.”

Things you can do in a XAML node loop

Writing a XAML node loop can walk through the set of nodes that come from a XamlReader.  It can then choose to do one of many things:

  • evaluate the data, report results.  (for example: a validator)
  • pass the XAML nodes to a writer (like XamlObjectWriter).  (you could build XamlPad like this...perhaps you'd want to strip out x:Class and any events...better than XamlPads of today)
  • pass the XAML nodes to a writer and adding additional nodes.  The Workflow designer adds linenumber and lineoffset information as attached properties to enable better debugging at runtime.
  • more...

Example of finding all named object instances

I built an example that uses a XAML node loop to help answer a WPF forum question "How to get x:Name from 3DElements using XamlReader.Load".

By using XamlXmlReader and XamlObjectWriter, and keeping track of names and instances of objects, we’re able to solve the scenario.

[Will upload this sample to http://robrelyea.com soon…]

   1: //This .NET 4 Beta 1 example was inspired by the WPF forum question at:
   2: //  http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/5c226430-c54d-45b8-a8a2-7e4a79e3692a
   3: //
   4:  
   5: /*
   6:  Console output of this example is:
   7:  XAML Contents:
   8: <Window
   9:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  10:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  11:     xmlns:local="clr-namespace:NodeLoopExample;assembly=NodeLoopExample"
  12:     >
  13:     <StackPanel>
  14:         <Button Name="button1">Ok</Button>
  15:         <Button x:Name="button2">No, it is not ok!</Button>
  16:         <Button>
  17:             <Button.Background>
  18:                 <LinearGradientBrush x:Name="brush">
  19:                     <GradientStop Color="AliceBlue" />
  20:                     <GradientStop Color="Red" Offset="1" />
  21:                 </LinearGradientBrush>
  22:             </Button.Background>
  23:             Button with color
  24:         </Button>
  25:     </StackPanel>
  26: </Window>
  27: 
  28: NamedObjects:
  29: Name: button1, Type: Button
  30: Name: button2, Type: Button
  31: Name: brush, Type: LinearGradientBrush
  32: --close the window to close the app--
  33: */
  34:  
  35: using System;
  36: using System.Collections.Generic;
  37: using System.Windows;
  38: using System.Xaml;
  39: using System.Xml;
  40: using System.IO;
  41:  
  42: namespace NodeLoopExample
  43: {
  44:     class Program
  45:     {
  46:         [STAThread]
  47:         static void Main(string[] args)
  48:         {
  49:             Console.WriteLine("XAML Contents:");
  50:             string xamlContents = File.ReadAllText("WindowWithNames.xaml");
  51:             Console.WriteLine(xamlContents);
  52:             Console.WriteLine();
  53:             
  54:             List<Frame> namedObjects = ShowObjectGraphAndHarvestNames("WindowWithNames.xaml");
  55:             Console.WriteLine("NamedObjects:");
  56:             foreach (Frame frame in namedObjects)
  57:             {
  58:                 Console.WriteLine("Name: {0}, Type: {1}", frame.Name, frame.Type.UnderlyingType.Name);
  59:             }
  60:  
  61:             Console.WriteLine("--close the window to close the app--");
  62:             Application app = new Application();
  63:             app.Run();
  64:         }
  65:  
  66:         //Let's loop through all the XAML nodes in this XAML file.
  67:         //We'll keep track of the tag name (Type), attributes or property elements (Member)
  68:         // names (Name) and instances (Instance) on a Frame class.
  69:         //We'll return all Frames with a name from this method.
  70:         //We also want to show the objectGraph created.
  71:         private static List<Frame> ShowObjectGraphAndHarvestNames(string fileName)
  72:         {
  73:             //we use a stack to keep track of names/instances
  74:             Stack<Frame> stack = new Stack<Frame>();
  75:  
  76:             XmlReader xr = XmlReader.Create(fileName);
  77:             XamlXmlReader reader = new XamlXmlReader(xr);
  78:             XamlObjectWriter writer = new XamlObjectWriter(reader.SchemaContext);
  79:  
  80:             //List of all frames which had names
  81:             List<Frame> namedObjects = new List<Frame>();
  82:  
  83:             while (reader.Read())
  84:             {
  85:                 writer.WriteNode(reader);
  86:  
  87:                 switch (reader.NodeType)
  88:                 {
  89:                     case XamlNodeType.StartObject:
  90:                         Frame newFrame = new Frame();
  91:                         newFrame.Type = reader.Type;
  92:                         stack.Push(newFrame);
  93:                         break;
  94:                     case XamlNodeType.GetObject:
  95:                         Frame newFrameForGet = new Frame();
  96:                         newFrameForGet.Type = stack.Peek().Member.Type;
  97:                         stack.Push(newFrameForGet);
  98:                         break;
  99:                     case XamlNodeType.StartMember:
 100:                         stack.Peek().Member = reader.Member;
 101:                         break;
 102:                     case XamlNodeType.EndMember:
 103:                         stack.Peek().Member = null;
 104:                         break;
 105:                     case XamlNodeType.EndObject:
 106:                         stack.Peek().Instance = writer.Result;
 107:                         if (stack.Peek().Name != null)
 108:                         {
 109:                             namedObjects.Add(stack.Peek());
 110:                         }
 111:                         stack.Pop();
 112:                         break;
 113:                     case XamlNodeType.Value:
 114:                         XamlMemberBase contentProperty = stack.Peek().Type.GetAliasedProperty(XamlLanguage.Name);
 115:                         if (stack.Peek().Member == contentProperty
 116:                             || stack.Peek().Member == XamlLanguage.Name)
 117:                         {
 118:                             stack.Peek().Name = reader.Value as string;
 119:                         }
 120:                         break;
 121:                 }
 122:             }
 123:  
 124:             object rootObject = writer.Result;
 125:             ShowInWindow(rootObject);
 126:             return namedObjects;
 127:         }
 128:  
 129:         private static void ShowInWindow(object root)
 130:         {
 131:             Window window = root as Window;
 132:             if (window == null)
 133:             {
 134:                 window = new Window();
 135:                 window.Content = root;
 136:             }
 137:             if (window.Title == "")
 138:             {
 139:                 window.Title = "NodeLoopExample";
 140:             }
 141:             window.Height = 400;
 142:             window.Width = 300;
 143:             window.Top = 400;
 144:             window.Left = 400;
 145:             window.Show();
 146:         }
 147:     }
 148:     class Frame
 149:     {
 150:         public XamlType Type { get; set; }
 151:         public XamlMemberBase Member { get; set; }
 152:         public string Name { get; set; }
 153:         public object Instance { get; set; }
 154:     }
 155: }

Sparky Dasrath writes a short post discussing XAML centric improvements in VS2010 XAML Editor and Property Grid for WPF and Silverlight developers.

Posted by Rob_Relyea | 1 comment(s)
Filed under: , , ,

I was working on a draft of a post about Events and XAML, and I tripped over this blast from the past from Kevin Dente from 2003 “Longhorn and Avalon

I predict that Avalon will be a smashing success with developers. Why? Because XAML is so darn fun to say. Zaml. Just kind of rolls off the tongue, doesn't it? Coupled with its partner in crime - BAML - it's got a one-two punch that just can't miss. Way more fun to say than SVG. Or XUL. Yep, best technology name since SCSI.

XAML BAML, baby.

It reminds me of my “Name Fluency” post from May 2006:

I was reading an article in the NY Times this morning and I ran across the term "name fluency".

The article discussed a study that looked at stock performance based on a number of issues included how pronounceable the name was.

Although I had never heard a term for this before now, I'm a big believer in name fluency.  Back when we had to pick a name for Avalon's markup language, many names were considered.  I favored XAML (eXtensible Avalon Markup Language).  A coworker was arguing for AXML (Avalon XML).

Name fluency was my reasoning.

Shortly before our largest customer preview of Avalon/XAML, perhaps in 2003, we decided to make the A stand for Application, since we didn't think the term Avalon was going to ever be shared externally.

Posted by Rob_Relyea | 2 comment(s)
Filed under:

I received a customer request for how a XAML downloaded from a server can still have events.  Let’s discuss the options for .NET 3.x, 4 and Silverlight.

Option 1: Compile your XAML (works in v3, v4, and Silverlight)

By default, when you add a XAML page into a project in VS or Blend, its BuildAction (shown in the properties pane – F4) is Page. When you build the assembly, MarkupCompilePass1 and MarkupCompilePass2 will analyze all the Pages and generate generated code files (.g.cs, .g.vb, etc…) and .baml files.  When you navigate to the url of the XAML or do a new of the class defined in the XAML, it will end up calling Application.LoadComponent, which will load the class and the baml file. Every element in XAML that has a name or an event handler declared will end up calling IComponentConnector.Connect() in the generated code file, where we will wire the field that matches any name on that element, and wires any events. I can go deeper in this space if necessary, but you could also read this old post: “XAML, BAML, .g.cs details

For Silverlight, there is no BAML, but we still create a generated code file and wire named objects to fields. Event wire up is handled by the parser.

Option 2: Don’t send UI from server, send data (works in v3, v4, and Silverlight)

Often people are used to sending HTML + Script from the server to the client. Since WPF (and Silverlight) has great databinding capabilities, you should rethink what you actually need to send down to the client for this kind of scenario. Often it is the data that changes often, and the UI is more static. You could Databind to downloaded XML data in an XmlDataProvider object.

Option 3a: Use XamlReader.Load() (no compiling) and specify event handlers in XAML (works in v4 and Silverlight)

In .NET 4, we have replaced the XAML plumbing for WPF. One of the new capabilities is that XamlReader.Load can handle event attributes declared in XAML.  V3 used to throw a parse exceptions.

<Button Click="handle_ok">Ok</Button>

V4 will look for an method on the root object of the XAML file called handle_ok.  So, if you know the set of methods you’ll have, you could send down a XAML with the appropriate root object and specify events as attributes in the XAML.

Option 3b: Use XamlReader.Load() (no compiling) and specify event handlers with a MarkupExtension (works in v4)

If having the methods located on the Root object doesn’t work for your scenario, v4 allows you to provide any markupextension that will return a Delegate as the value for an event in XAML.

<Button Click="{Run MyRoutine,Ok}">Ok</Button

You could build the RunExtension class so that it finds MyRoutine anywhere you want. You can also pass along parameters to that event, as you please.

Option 3c: Use XamlReader.Load() (no compiling) and specify event handlers with a DLR language via a markup extension (works in V4)

One could build a markupextension that can run code, perhaps with the DLR.

<Button Click="{RunCode 'textBox1.Text = lastName.Text + ", " + firstName.Text;'}">ok</Button>

The RunCodeExtension would need to know how to call into the DLR, and it would also need to use IXamlNameResolver to have access to the set of named objects that might be refered to in code.

Option 4: Integrate the DLR via a markup extension (works in v3 and v4)

In 2008, Daniel Paul wrote a 6 part series that explores adding DLR code into XAML.  Part 5 explores writing Event handlers.

Option 5: Use XamlReader.Load, and wire events with FindName afterwards (works in v3)

See style #2 in “3 Coding Styles for Avalon Applications

Posted by Rob_Relyea | 5 comment(s)
Filed under: ,
Internal thread that may be of use to more people: 

Question From: Microsoft Developer

Is there a way to configure XAML writer (XamlWriter.Save() or XamlServices.Save()) to not to serialize null properties. Right now I get "{x:Null}" written for them?

 

Answer From: XAML Team Developer

You need to attribute the affected properties with [DefaultValue(null)]

  

Mike Hillberg's "Being Written by XamlWriter" is a good read for this kind of info

 

--

Posted by Rob_Relyea | 4 comment(s)
Filed under:

Krishna Bhargav shows how to declare String.Empty() in XAML in “Declaring Empty String in XAML”.  XAML 2009 will continue with this same behavior.

Posted by Rob_Relyea | 1 comment(s)
Filed under:

XAML 2006 Background

XAML design in .NET 3 was mostly driven by WPF scenarios.  In that timeframe, WPF only had one significant generic type that required its use in XAML: PageFunction<T>.  As such, we supported x:TypeArguments on the root element of compiled XAML files.  We knew there were 2 further steps for Generic support to do, but skipped them for v3:

  • Support instantiation of a generic type in any XAML load. (coming in XAML 2009)
  • Support providing x:TypeParameters to compiled XAML. (not coming in XAML 2009)

XAML 2009 Support for Generic Types

Since .NET 4 is seeing XAML usage broaden into other areas with System.Xaml.dll and XAML 2009 language features, it became more important for XAML to support Generic types (a CLR 2 feature!).

This example uses WPF generic types (just because I’m most adept with those tags):

<Window>
    <Window.DataContext>
        <ObservableCollection x:TypeArguments="my:Person">
            <my:Person FirstName="Tom" LastName="Holiday" />
            <my:Person FirstName="Joan" LastName="Holiday" />
        </ObservableCollection>
    </Window.DataContext>
    …
</Window>

This could be accomplished in C# by:

Window win = new Window {
  DataContext = new ObservableCollection<Person> {
       new Person { FirstName="Tom", LastName="Holiday" },
       new Person { FirstName="Joan", LastName="Holiday" }
   }
};

(thanks to Dave's comment who gave me more concise C# syntax and Simon's comment who removed an extra "new").

Multiple Type Arguments

In order to support multiple type arguments, use a comma to delimit:

<Dictionary x:TypeArguments="x:String, x:Double">

</Dictionary>

Nesting of Type Arguments

If you wanted to have a Dictionary of Strings map to lists of Doubles, you would use parenthesis (since <> are kindof taken in XML):

<Dictionary x:TypeArguments="x:String, sys:List(x:Double)">

</Dictionary>

Node Stream Representation

For those of you looking at the nodes that XamlXmlReader (or other XamlReaders) produce, x:TypeArguments appears to disappear from the stream of nodes.  This is by design…a XamlReader would expose the ObservableCollection element above as:

  • xamlReader.NodeType: StartObject
  • xamlReader.Type: ObservableCollection(my:Person)

In the PDC2008 XAML talk, we go over the Node Stream…but I haven’t blogged about it yet…

Remember

Most XAML 2009 Features won’t work in compiled XAML scenarios in .NET 4, but will work with uncompiled XAML.  See details here…”Yes, XAML2009 isn’t everywhere yet…

Posted by Rob_Relyea | 9 comment(s)
Filed under: ,
Page view counter