Thursday, September 13, 2007

Enumerating XAML (BAML) files in an Assembly

I wanted to play around with XAML Pages, and wanted an application that automagically showed me a list of all included (embedded) XAML files in an assembly without me doing more than adding the XAML file to the project.

This turned out to be tricky until you got it...

Well, it is simple when you know how to do it, but until then you'll have some googling to do. (At least that is  my experience)

Basically, a XAML file with a build action of Page or Resource ends up in a .resources file named <assemblyname>.g.resources. This .resources file contains the binary version of the XAML file (BAML).

Enumerating the contents of a .resources file can be done using the System.Resources.ResourceReader class as follows:

using (ResourceReader reader = new ResourceReader("Foo.g.resources"))
{
foreach (DictionaryEntry entry in reader)
{
Console.WriteLine(entry.Key);
}
}

So all that's left to do is to create the application, (and no, it ain't pretty but it works):


MainForm.xaml:

<Window x:Class="AutoToc.MainForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=system"
Title="AutoToc" Height="400" Width="600" Loaded="FormLoaded"
>
<
Window.Resources>
<
HierarchicalDataTemplate DataType="{x:Type sys:Uri}">
<
BulletDecorator VerticalAlignment="Center">
<
BulletDecorator.Bullet>
<
Ellipse Fill="BlueViolet" Width="5" Height="5"
VerticalAlignment="Center"/>
</
BulletDecorator.Bullet>
<
TextBlock Text="{Binding Path=OriginalString}"
Margin="5,0,0,0" VerticalAlignment="Center" />
</
BulletDecorator>
</
HierarchicalDataTemplate>
</
Window.Resources>
<
Grid>
<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="200"/>
<
ColumnDefinition Width="Auto"/>
<
ColumnDefinition Width="*"/>
</
Grid.ColumnDefinitions>
<
TreeView Grid.Column="0" Name="tocTree"
ItemsSource="{Binding}"
SelectedItemChanged="tocTreeSelectedItemChanged"></TreeView>
<
GridSplitter Grid.Column="1" Width="2" HorizontalAlignment="Left"
VerticalAlignment="Stretch"/>
<
Frame Name="contentFrame" Grid.Column="2" NavigationUIVisibility="Hidden"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</
Grid>
</
Window>

and the interesting parts of the code behind:

private void FormLoaded(object sender, RoutedEventArgs e)
{
// UriCollection inherits ObservableCollection<Uri>
UriCollection pages = new UriCollection();
Assembly asm = Assembly.GetExecutingAssembly();
Stream stream = asm.GetManifestResourceStream(asm.GetName().Name + ".g.resources");

DataContext = pages;
using (ResourceReader reader = new ResourceReader(stream))
{
foreach (DictionaryEntry entry in reader)
{
// for some curious reason, we get "cannot locate resource"
// if we leave the .baml extension, it needs to be .xaml !?!
pages.Add(new Uri(((string)entry.Key).Replace(".baml", ".xaml"),
UriKind.Relative));

}
}
}

private void tocTreeSelectedItemChanged(object sender, RoutedEventArgs e)
{
contentFrame.Source = tocTree.SelectedItem as Uri;
}

That was easy, wasn't it?


Obviously this is not production quality code but more a proof of concept, and yes, I do use it for playing around with the 3D samples...


Obviously, it would be better to filter out non-Page xaml files, such as mainform ...

No comments: