We often use Xaml to instantiate and initialize objects. For example, given “<Foo Bar=’1’/>”, a Xaml loader creates a Foo object, and sets the Bar property to 1. That works when the Bar property is settable, but what can you do if it isn’t?
An example of this scenario in .Net today shows up with strings; the String type is immutable, it doesn’t have any settable properties. But sometimes you want to build a string with formats, concatenation, etc. Thus the invention of the StringBuilder class, where the properties have setters (and the methods can mutate an instance).
You can follow the same idea in Xaml by creating builder types, using markup extensions.
Here’s an example of a type whose properties can’t be set except internally, since it has read-only properties:
<Assembly: XmlnsDefinition("http://blogs.msdn.com/mikehillberg/BuilderPattern", "PeopleNamespace")>
Public Class Person
Private _name As String
Public ReadOnly Property Name() As String
Private _age As Integer
Public ReadOnly Property Age() As Integer
Public Shared Function Create(ByVal Name As String, ByVal Age As Integer) As Person
Dim person As New Person
person._name = Name
person._age = Age
(I’m using the XmlnsDefinition assembly attribute here so that I can have a normal-looking Uri in my Xaml, but you can also make references directly to an assembly using the “clr-namespace” syntax, as described here.)
For demonstration, also define a List(Of Person) named “People”:
Public Class People
Inherits List(Of Person)
… and then, for example, you can’t load this Xaml, because of the read-only properties:
<People xmlns="http://blogs.msdn.com/mikehillberg/BuilderPattern" >
<Person Name='Mike' Age='5' />
<Person Name='Dwayne' Age='7' />
(This gives an error of “'Name' property is read-only and cannot be set from markup”).
But we can create a markup extension that follows the builder pattern to create the object.
First, define the markup extension, named PersonExtension. It looks just like the Person type, except it has setters for the properties, and overrides the ProvideValue function:
<Assembly: XmlnsDefinition("http://blogs.msdn.com/mikehillberg/BuilderPatternBuilder", "PeopleBuilderNamespace")>
Public Class PersonExtension
Public Property Name() As String
Set(ByVal value As String)
_name = value
Public Property Age() As Integer
Set(ByVal value As Integer)
_age = value
Public Overrides Function ProvideValue(ByVal serviceProvider As IServiceProvider) As Object
Return Person.Create(Name, Age)
And now we can modify the markup slightly so that it successfully loads:
<builder:Person Name='Mike' Age='5' />
<builder:Person Name='Dwayne' Age='7' />
To test it out, run this code:
Dim people As People
people = XamlReader.Load(File.Open("..\..\Test.xaml", FileMode.Open))
For Each person As Person In people
Console.WriteLine("Name = " + person.Name + ", Age = " + person.Age.ToString())
… and we get this output:
Name = Mike, Age = 5
Name = Dwayne, Age = 7
One last note about markup extensions … Notice that I named the builder class “PersonExtension”, but I referred to it from Xaml as “Person”. That’s possible because when Xaml doesn’t find “Person”, it automatically appends the “Extension” suffix, and looks for “PersonExtension”. If Person and PersonExtension had been in the same Clr namespace, that would have caused confusion, so I put them in different namespaces (“PeopleNamespace” and “PeopleBuilderNamespace”). Alternatively, I could have put them both in the “PeopleNamespace”, and used the specific <PersonExtension> tag (or maybe I would have named it “PersonBuilder”).
PingBack from http://hubsfunnywallpaper.cn/?p=468