Home About Eric Topics SourceGear

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.