Tuesday, October 13, 2009

Silverlight 3 XamlWriter - A basic implementation

I am sure that anyone who has worked extensively with Silverlight has felt the need for a XamlWriter at some time. I mostly need it for debugging purposes - to see the xaml of the dynamically assembled controls.

I have been taking the cumbersome route of stepping through the control hierarcy using the debugger, but enough is enough! Just takes too much time. It probably is quicker to just write some code which takes in a FrameworkElement and outputs the Xaml representation of it.

I did find 3 resources on the web that would prove useful (or not!)
  1. XamlWriter by Mehran (rambler.elf on the Silverlight.net forums) - A nice simple reflection based approach that would prove the starting point for my project
  2. XamlWriter by the SilverlightContrib team - Did not try this since it is embedded deep in another project. Not sure if this wuld work for me but since I like to have more control I decided to write one myself (based on Mehran's code)
  3. Silverlight Spy - A third party tool that could be used to extract the Xaml of a specific control. Not so useful for me since I need to hook it in my code
So I decided to take Mehran's code and make it work on Silverlight 3. Then I refined it and added some features and refined it a bit more and added some more features and...

It's by no means complete or well tested but if you feel brave enough
  • just download this file - XamlWriter.dll 
  • add a reference to this file from your project 
  • and use the following snippet of code to test it

using projectsilverlight.blogspot.com.XamlWriter;

XamlWriter xamlWriter = new XamlWriter();
string xaml = xamlWriter.WriteXaml(MyControl, XamlWriterSettings.LogicalTree);

Note that XamlWriter uses System.Xml.Linq for some processing so you will find this dll getting added to the xap as well.

XamlWriterSettings has 3 values which can be combined to get different output. The 4 valid combinations are

WriteXaml(MyControl, XamlWriterSettings.LogicalTree);
WriteXaml(MyControl, XamlWriterSettings.LogicalTree | XamlWriterSettings.AllAttributes);
WriteXaml(MyControl, XamlWriterSettings.VisualTree);
WriteXaml(MyControl, XamlWriterSettings.VisualTree | XamlWriterSettings.AllAttributes);
Like I mentioned before - this is not well tested. In fact, the example below is the only case I have tested. I am putting it up nevertheless to get some feedback. If you find that it is not working for a specific case, send me the Xaml/code that it is not working for and I will take a look. At some point in the future, if still relevant, I will open source this code. But now it is closed source for various reasons - the code quality sucks, haven't taken permission from Mehran yet, want better control so that a single codebase gets improved upon rather than each individual fixing it for his/her needs.

Enjoy!

An example


Assuming an Xaml like this

<UserControl x:Class="test2.MainPage" x:Name="MyControl"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 Width="850" Height="447">
    <Grid x:Name="test">
        <Grid.RowDefinitions>
            <RowDefinition Height="60"/>
            <RowDefinition Height="54"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"/>
            <ColumnDefinition Width="54"/>
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Border x:Name="ContentBorder" BorderThickness="0,3,0,0" BorderBrush="#FFAFAFAF" Margin="0,58,0,0" Opacity="0.5" 
                Grid.Row="1" Grid.Column="2">
            <Grid x:Name="CurrentContent">
                <Button x:Name="tt" Width="100" Height="100" Click="Button_Click"/>
            </Grid>
        </Border>
        <Button x:Name="zz" Width="100" Height="100" Click="Button_Click"/>
    </Grid>
</UserControl>


WriteXaml(MyControl, XamlWriterSettings.LogicalTree) will generate this Xaml

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="850" Height="447" x:Name="MyControl">
  <Grid x:Name="test">
    <Grid.RowDefinitions>
      <RowDefinition Height="60" />
      <RowDefinition Height="54" />
      <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="60" />
      <ColumnDefinition Width="54" />
      <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Border BorderThickness="0,3,0,0" Margin="0,58,0,0" x:Name="ContentBorder" Opacity="0.5" Grid.Row="1" Grid.Column="2" BorderBrush="#FFAFAFAF">
      <Grid x:Name="CurrentContent">
        <Button Width="100" Height="100" x:Name="tt" />
      </Grid>
    </Border>
    <Button Width="100" Height="100" x:Name="zz" />
  </Grid>
</UserControl>

WriteXaml(MyControl, XamlWriterSettings.LogicalTree | XamlWriterSettings.AllAttributes) will generate this Xaml

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" IsTabStop="False" IsEnabled="True" TabIndex="2147483647" TabNavigation="Local" Padding="0,0,0,0" BorderThickness="0,0,0,0" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="Portable User Interface" FontSize="11" FontStretch="Normal" FontStyle="Normal" FontWeight="Normal" Width="850" Height="447" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" x:Name="MyControl" Opacity="1" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True">
  <MainPage.Foreground>
    <SolidColorBrush Color="#FF000000" Opacity="1" />
  </MainPage.Foreground>
  <Grid ShowGridLines="False" Width="NaN" Height="NaN" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" x:Name="test" Opacity="1" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True">
    <Grid.RowDefinitions>
      <RowDefinition Height="60" MaxHeight="Infinity" MinHeight="0" />
      <RowDefinition Height="54" MaxHeight="Infinity" MinHeight="0" />
      <RowDefinition Height="1*" MaxHeight="Infinity" MinHeight="0" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="60" MaxWidth="Infinity" MinWidth="0" />
      <ColumnDefinition Width="54" MaxWidth="Infinity" MinWidth="0" />
      <ColumnDefinition Width="1*" MaxWidth="Infinity" MinWidth="0" />
    </Grid.ColumnDefinitions>
    <Border BorderThickness="0,3,0,0" CornerRadius="0,0,0,0" Padding="0,0,0,0" Width="NaN" Height="NaN" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,58,0,0" x:Name="ContentBorder" Opacity="0.5" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True" Grid.Row="1" Grid.Column="2">
      <Border.BorderBrush>
        <SolidColorBrush Color="#FFAFAFAF" Opacity="1" />
      </Border.BorderBrush>
      <Grid ShowGridLines="False" Width="NaN" Height="NaN" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" x:Name="CurrentContent" Opacity="1" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True">
        <Button ClickMode="Release" IsTabStop="True" IsEnabled="True" TabIndex="2147483647" TabNavigation="Local" Padding="3,3,3,3" BorderThickness="1,1,1,1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="Portable User Interface" FontSize="11" FontStretch="Normal" FontStyle="Normal" FontWeight="Normal" Width="100" Height="100" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" x:Name="tt" Opacity="1" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True">
          <Button.Background>
            <SolidColorBrush Color="#FF1F3B53" Opacity="1" />
          </Button.Background>
          <Button.BorderBrush>
            <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" SpreadMethod="Pad" MappingMode="RelativeToBoundingBox" ColorInterpolationMode="SRgbLinearInterpolation" Opacity="1">
              <LinearGradientBrush.GradientStops>
                <GradientStop Color="#FFA3AEB9" Offset="0" />
                <GradientStop Color="#FF8399A9" Offset="0.375" />
                <GradientStop Color="#FF718597" Offset="0.375" />
                <GradientStop Color="#FF617584" Offset="1" />
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Button.BorderBrush>
          <Button.Foreground>
            <SolidColorBrush Color="#FF000000" Opacity="1" />
          </Button.Foreground>
        </Button>
      </Grid>
    </Border>
    <Button ClickMode="Release" IsTabStop="True" IsEnabled="True" TabIndex="2147483647" TabNavigation="Local" Padding="3,3,3,3" BorderThickness="1,1,1,1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontFamily="Portable User Interface" FontSize="11" FontStretch="Normal" FontStyle="Normal" FontWeight="Normal" Width="100" Height="100" MinWidth="0" MaxWidth="Infinity" MinHeight="0" MaxHeight="Infinity" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" x:Name="zz" Opacity="1" RenderTransformOrigin="0,0" IsHitTestVisible="True" Visibility="Visible" UseLayoutRounding="True">
      <Button.Background>
        <SolidColorBrush Color="#FF1F3B53" Opacity="1" />
      </Button.Background>
      <Button.BorderBrush>
        <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1" SpreadMethod="Pad" MappingMode="RelativeToBoundingBox" ColorInterpolationMode="SRgbLinearInterpolation" Opacity="1">
          <LinearGradientBrush.GradientStops>
            <GradientStop Color="#FFA3AEB9" Offset="0" />
            <GradientStop Color="#FF8399A9" Offset="0.375" />
            <GradientStop Color="#FF718597" Offset="0.375" />
            <GradientStop Color="#FF617584" Offset="1" />
          </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
      </Button.BorderBrush>
      <Button.Foreground>
        <SolidColorBrush Color="#FF000000" Opacity="1" />
      </Button.Foreground>
    </Button>
  </Grid>
</UserControl>

WriteXaml(MyControl, XamlWriterSettings.VisualTree) will generate this Xaml

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="850" Height="447" x:Name="MyControl">
  <Grid x:Name="test">
    <Grid.RowDefinitions>
      <RowDefinition Height="60" />
      <RowDefinition Height="54" />
      <RowDefinition Height="1*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="60" />
      <ColumnDefinition Width="54" />
      <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Border BorderThickness="0,3,0,0" Margin="0,58,0,0" x:Name="ContentBorder" Opacity="0.5" Grid.Row="1" Grid.Column="2" BorderBrush="#FFAFAFAF">
      <Grid x:Name="CurrentContent">
        <Button Width="100" Height="100" x:Name="tt">
          <Grid>
            <Border BorderThickness="1,1,1,1" CornerRadius="3,3,3,3" x:Name="Background" Background="#FFFFFFFF">
              <Border.BorderBrush>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                  <LinearGradientBrush.GradientStops>
                    <GradientStop Color="#FFA3AEB9" />
                    <GradientStop Color="#FF8399A9" Offset="0.375" />
                    <GradientStop Color="#FF718597" Offset="0.375" />
                    <GradientStop Color="#FF617584" Offset="1" />
                  </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
              </Border.BorderBrush>
              <Grid Margin="1,1,1,1" Background="#FF1F3B53">
                <Border x:Name="BackgroundAnimation" Opacity="0" Background="#FF448DCA" />
                <Rectangle x:Name="BackgroundGradient">
                  <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0.699999988079071,0" EndPoint="0.699999988079071,1">
                      <LinearGradientBrush.GradientStops>
                        <GradientStop Color="#FFFFFFFF" />
                        <GradientStop Color="#F9FFFFFF" Offset="0.375" />
                        <GradientStop Color="#E5FFFFFF" Offset="0.625" />
                        <GradientStop Color="#C6FFFFFF" Offset="1" />
                      </LinearGradientBrush.GradientStops>
                    </LinearGradientBrush>
                  </Rectangle.Fill>
                </Rectangle>
              </Grid>
            </Border>
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,3,3,3" x:Name="contentPresenter" />
            <Rectangle RadiusX="3" RadiusY="3" x:Name="DisabledVisualElement" Opacity="0" IsHitTestVisible="False" Fill="#FFFFFFFF" />
            <Rectangle RadiusX="2" RadiusY="2" Margin="1,1,1,1" x:Name="FocusVisualElement" Opacity="0" IsHitTestVisible="False" Stroke="#FF6DBDD1" />
          </Grid>
        </Button>
      </Grid>
    </Border>
    <Button Width="100" Height="100" x:Name="zz">
      <Grid>
        <Border BorderThickness="1,1,1,1" CornerRadius="3,3,3,3" x:Name="Background" Background="#FF6DBDD1">
          <Border.BorderBrush>
            <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
              <LinearGradientBrush.GradientStops>
                <GradientStop Color="#FFA3AEB9" />
                <GradientStop Color="#FF8399A9" Offset="0.375" />
                <GradientStop Color="#FF718597" Offset="0.375" />
                <GradientStop Color="#FF617584" Offset="1" />
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>
          </Border.BorderBrush>
          <Grid Margin="1,1,1,1" Background="#FF1F3B53">
            <Border x:Name="BackgroundAnimation" Background="#FF448DCA" />
            <Rectangle x:Name="BackgroundGradient">
              <Rectangle.Fill>
                <LinearGradientBrush StartPoint="0.699999988079071,0" EndPoint="0.699999988079071,1">
                  <LinearGradientBrush.GradientStops>
                    <GradientStop Color="#D8FFFFFF" />
                    <GradientStop Color="#C6FFFFFF" Offset="0.375" />
                    <GradientStop Color="#8CFFFFFF" Offset="0.625" />
                    <GradientStop Color="#3FFFFFFF" Offset="1" />
                  </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
              </Rectangle.Fill>
            </Rectangle>
          </Grid>
        </Border>
        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Margin="3,3,3,3" x:Name="contentPresenter" />
        <Rectangle RadiusX="3" RadiusY="3" x:Name="DisabledVisualElement" Opacity="0" IsHitTestVisible="False" Fill="#FFFFFFFF" />
        <Rectangle RadiusX="2" RadiusY="2" Margin="1,1,1,1" x:Name="FocusVisualElement" IsHitTestVisible="False" Stroke="#FF6DBDD1" />
      </Grid>
    </Button>
  </Grid>
</UserControl>

WriteXaml(MyControl, XamlWriterSettings.VisualTree | XamlWriterSettings.AllAttributes) will generate this Xaml

-- You get the idea! (too big to display here)

4 comments:

Anonymous said...

This is fantastic! Just what I was looking for. Thanks Wilfred!

Gan said...

Hi
I've tried to create XAML using this DLL for the Visifire Chart (which i need to duplicate)
i get the following exception if i use VisualTree as argument ->
Unable to cast object of type 'System.Windows.Point' to type 'System.Windows.DependencyObject'. and the stacktrace showing ->
at projectsilverlight.blogspot.com.XamlWriter.XamlWriter.writeCollections(DependencyObject target, DependencyObject targetBlank)
at
.....
projectsilverlight.blogspot.com.XamlWriter.XamlWriter.WriteXaml(Object parent, DependencyObject parentBlank)
at projectsilverlight.blogspot.com.XamlWriter.XamlWriter.WriteXaml(Object parent)
at (Object parent)
at projectsilverlight.blogspot.com.XamlWriter.XamlWriter.WriteXaml(Object parent, XamlWriterSettings xamlSettings)
at SLSave2Image.MainPage.ButtonSave_Click(Object sender, RoutedEventArgs e)
at System.Windows.Controls.Primitives.ButtonBase.OnClick()
at System.Windows.Controls.Button.OnClick()
at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, String eventName)

and i use LogicalTree I get the following msg -> System.StackOverflowException was unhandled
.

Any thoughts.

Anonymous said...

Your DLL really works great with the exception of writing all namespace declarations. Do you plan to add functionality to read control declarations such as those found in the Silverlight Toolkit?

Tadej said...

Hi, DLL is great.

What's about events? For example this XAML:



In my case, i need to get XAML with events.