Tip: Using XamlWriter and XamlReader to clone an object
There are multiple ways to clone objects, and multiple definitions of what “clone” should even mean.
The main issue is usually about cloning “deep” vs. “shallow”. For example, if you have a Customer object that points to an Address object, and you clone the Customer object, does the Address object get cloned too (this would be a deep clone)? Or does the new Customer reference the original Address object (a shallow clone)?
Another issue is whether you clone just the properties of the object, or the fields instead. And do you clone event listeners?
Yet another issue is that you might clone a property value on the object that is supposed to be unique, such as the Name property on WPF elements.
Anyway, here’s a trick to clone an object by writing it out to Xaml then reading it back in. As such, the cloning rules it follows matches the Xaml serialization rules (see here for some more info). That primarily means that it only clones public properties – no fields or events – and it clones deep.
So here’s what the code might look like:
private Object CloneUsingXaml(Object o)
{
string xaml = XamlWriter.Save(o);
return XamlReader.Load(new XmlTextReader(new StringReader(xaml)));
}
(XamlReader.Load doesn’t have an overload that takes a string, so you need to convert the string into an XmlReader.)
Here’s an example. Take this app:
<Window x:Class="WPFApplication1.Window1"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300" Loaded='OnLoaded'>
<Grid Name='Grid1'>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle Name='_rectangle1' Fill='Red' Width='20' Height='20' />
</Grid>
</Window>
… and clone the Rectangle during load with:
private void OnLoaded(object sender, RoutedEventArgs args)
{
Rectangle rectangle2 = CloneUsingXaml(_rectangle1) as Rectangle;
rectangle2.Name = null;
Grid.SetColumn(rectangle2, 1);
Grid1.Children.Add(rectangle2);
}
… and you get a Window with two rectangles.
Here’s what the intermediate Xaml looks like:
<Rectangle
Fill="#FFFF0000"
Name="_rectangle1"
Width="20" Height="20"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" />
Note here that the Name property got cloned, which isn’t what we want, so I removed the Name after running the clone.
Comments
Anonymous
December 12, 2007
PingBack from http://anoriginalidea.wordpress.com/2007/12/13/weird-science-defining-winforms-in-xaml/Anonymous
July 18, 2008
@"(XamlReader.Load doesn’t have an overload that takes a string, so you need to convert the string into an XmlReader.)" WPF doesn't, but Silverlight does. Unfortunately, the documentation for Silverlight's XamlReader.Load(string) overload is AWFUL. It's not obvious or mentioned anywhere in the Silverlight documentation, but it seems every string representation of xml passed into XamlReader.Load(string) must be prepended with the following C# string: @"<?Mapping XmlNamespace=""local"" ClrNamespace=""your-application-namespace"" Assembly=""your-assembly"" ?>" Even so, I've been having trouble getting it to correctly parse/use the xaml I supply it.Anonymous
April 29, 2010
The comment has been removedAnonymous
April 30, 2010
akjoshi -- Could you send me some sample code to take a look?