2007-07-19 13:00:00
7: XAML
This entry is part 7 of a 12-part series on WPF 3D.
Copying a WPF 3D Scene to the Clipboard as XAML
Clipboard.SetText(XamlWriter.Save(myViewport3D));
The one-line solution above actually does work. But in my situation, I had to do a bit more to get exactly what I wanted. I like to be able to click the Copy button in Sawdust and then just Paste into XAML Cruncher. Once again, ScreenSpaceLines3D got in my way.
First of all, just as I did in the bitmap case in part 3, I prepare a Viewport3D offscreen, give it a size, and then call Arrange(), Measure() and UpdateLayout() to get it all ready to draw. Then I call every instance of ScreenSpaceLines3D and tell them all to rescale.
The problem is that the XAML created by XamlWriter will have <ScreenSpaceLines3D> tags. This is the correct thing for XamlWriter to do, but it's not what I want. When I paste the result into XAML Cruncher, it doesn't work because the ScreenSpaceLines3D class isn't available. I believe XAML Cruncher has a way to load other DLLs for this kind of situation, but I really just want my XAML to be more generic.
ScreenSpaceLines3D is a subclass of ModelVisual3D. So it's really just a ModelVisual3D except that it has extra methods to rebuild its geometry as needed. For the purpose of exporting to XAML, I just want a ModelVisual3D which contains a snapshot of the geometry in its current state. So, I do this:
List<ScreenSpaceLines3D>
remove =
new List<ScreenSpaceLines3D>();
List<ModelVisual3D>
add =
new List<ModelVisual3D>();
foreach (ModelVisual3D
mv3d in myViewport3D.Children)
{
if (mv3d is
ScreenSpaceLines3D)
{
ScreenSpaceLines3D ss = mv3d as ScreenSpaceLines3D;
ModelVisual3D plain = new ModelVisual3D();
plain.Content = ss.Content;
add.Add(plain);
remove.Add(ss);
}
}
foreach (ModelVisual3D
mv3d in add)
{
myViewport3D.Children.Add(mv3d);
}
foreach (ScreenSpaceLines3D
ss in remove)
{
myViewport3D.Children.Remove(ss);
}
This gets me very close to what I want. I can take this Viewport3D, generate the XAML and paste it into XAML Cruncher, and it mostly works. I just want two more changes.
First, I want the XAML to be indented. The single argument version of XamlWriter.Save() returns XAML in its most compact form, with no indentation. So, instead, I have to use one of the other overloads to XamlWriter.Save(), the one that writes to an XmlTextWriter, something like this:
StringBuilder sb = new StringBuilder();
TextWriter tw = new
StringWriter(sb);
XmlTextWriter xw = new
XmlTextWriter(tw);
xw.Formatting = Formatting.Indented;
XamlWriter.Save(myViewport3D, xw);
xw.Close();
string xaml = sb.ToString();
Finally, I really don't want the Viewport3D in the XAML output to have a fixed size. Because I gave it a size and forced it through the Arrange/Measure/UpdateLayout process, the resulting XAML includes the size attributes on the Viewport3D tag. When I paste the whole thing into XAML Cruncher, it honors the requested size whether it fits or not, but I want the picture to automatically size to the available space. So...
xaml = xaml.Replace(
string.Format(
"<Viewport3D Height=\"{0}\" Width=\"{1}\"
",
myViewport.height, myViewport.width),
"<Viewport3D ");
If I wanted this to be a bit more robust, I might parse the XAML and remove the two attributes using the XML APIs, but you get the idea.
The resulting XAML file containing a picture of the workbench is here.