XAML renders differently
I was reading with great interest Keith's posts about creating a color chart in ASP.NET and decided to roll out a similar WPF application in pure code first - in Boo ofcourse - and in XAML second. The former will be a Windows application and the latter will be an XBAP application so I can run it inside a browser - just like Keith's demo.
The motivation for this came about when I realized that the color chart can easily be rendered using the WPF ListBox. The trick was to set the ItemsPanel property of the ListBox to a template containing a WPF UniformGrid.
Coding the windows version in Boo was a breeze. All in all it took only about 20 minutes to get it up and running since Keith had mapped the whole thing already. I used Keith's original method of using Reflection to get the list of predefined colors and adapted it for WPF.
class ColorService:
static def GetBrushes() as System.Collections.IEnumerable:
for propertyInfo as PropertyInfo in typeof(Brushes).GetProperties():
memberInfo = cast(MemberInfo, propertyInfo)
brush = cast(Brush,propertyInfo.GetValue(null,null))
yield NamedBrush(memberInfo.Name, brush)
class NamedBrush:
[getter(Name)] _name as string
[getter(Brush)] _brush as Brush
def constructor(name as string, brush as Brush):
_name = name
_brush = brush
override def ToString():
return _name
Here's the c# version:
public class ColorService
{
static public IEnumerable GetBrushes()
{
foreach (PropertyInfo propertyInfo in typeof(Brushes).GetProperties())
{
MemberInfo memberInfo = (MemberInfo)propertyInfo;
Brush brushValue = (Brush)propertyInfo.GetValue(null, null);
yield return new NamedBrush(memberInfo.Name, brushValue);
}
}
public class NamedBrush
{
public NamedBrush(string name, Brush brush)
{
_name = name;
_brush = brush;
}
public string Name
{
get { return _name; }
}
public Brush Brush
{
get { return _brush; }
}
public override string ToString()
{
return _name;
}
string _name;
Brush _brush;
}
}
Here's how the windows app looks like:
Now converting the app into XBAP took quite some time to finish since I'm not well versed in XAML yet. I had to check the Boo layout code repeatedly to get the XAML finally working. Heres the XAML layout code:
<Page x:Class="ColorChart.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Page1"
>
<Page.Resources>
<Style x:Key="CCStyle" >
<Setter Property="Control.BorderBrush" Value="LightGray"/>
<Setter Property="Control.BorderThickness" Value="1"/>
<Setter Property="Control.Margin" Value="3"/>
</Style>
<ItemsPanelTemplate x:Key="CCPanel">
<UniformGrid Columns="6"></UniformGrid>
</ItemsPanelTemplate>
<DataTemplate x:Key="CCTemplate">
<StackPanel>
<Rectangle
Width="100"
Height="40"
Fill="{Binding Path=Brush}"
HorizontalAlignment="Left"/>
<TextBlock
FontSize="11"
Text="{Binding Path=Name}"
HorizontalAlignment="Center">
</TextBlock>
</StackPanel>
</DataTemplate>
</Page.Resources>
<ListBox
Name="listBox1"
SelectionMode="Single"
ItemsPanel="{StaticResource CCPanel}"
ItemTemplate="{StaticResource CCTemplate}"
ItemContainerStyle="{StaticResource CCStyle}"/>
</Page>
This is the c# code-behind for the XAML above:
namespace ColorChart
{
public partial class Page1 : System.Windows.Controls.Page
{
public Page1()
{
InitializeComponent();
this.listBox1.ItemsSource = ColorService.GetBrushes();
}
}
}
And here's the layout code in Boo which does thesame thing as the XAML above:
class CCStyle(Style):
def constructor():
s1 = Setter(ListBoxItem.BorderThicknessProperty, Thickness(1))
s2 = Setter(ListBoxItem.MarginProperty, Thickness(3))
s3 = Setter(ListBoxItem.BorderBrushProperty, Brushes.LightGray)
s4 = Setter(ListBoxItem.HorizontalAlignmentProperty, HorizontalAlignment.Center)
self.Setters.Add(s1)
self.Setters.Add(s2)
self.Setters.Add(s3)
self.Setters.Add(s4)
class CCPanel(ItemsPanelTemplate):
def constructor():
root = FrameworkElementFactory(UniformGrid)
root.SetValue(UniformGrid.ColumnsProperty, 6)
self.VisualTree = root
class CCTemplate(DataTemplate):
def constructor():
root = FrameworkElementFactory(StackPanel)
ch1 = FrameworkElementFactory(Rectangle)
ch1.SetValue(Rectangle.WidthProperty, 100.0)
ch1.SetValue(Rectangle.HeightProperty, 40.0)
ch1.SetValue(Rectangle.FillProperty, Binding("Brush"))
ch2 = FrameworkElementFactory(TextBlock)
ch2.SetValue(TextBlock.TextProperty, Binding("Name"))
ch2.SetValue(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center)
root.AppendChild(ch1)
root.AppendChild(ch2)
self.VisualTree = root
class ColorChartView(ListBox):
def constructor():
ItemContainerStyle = CCStyle()
ItemsPanel = CCPanel()
ItemTemplate = CCTemplate()
ItemsSource = ColorService.GetBrushes()
An XBAP application needs to be compiled and then published to an IIS server first. After you have done that open your IE browser - or Firefox browser if you have the IE Tab extension installed - and type in the url of your application. In my case I typed in http://localhost/winfx/colorchart.xbap and here's what came out:

Now that was neat. The keyboard navigation and mouse selection works just like in the windows version. But if you will look at it more closely you'll notice that there's something different about the XAML rendered view.
XAML by the way is tokenized into BAML during compilation and is embedded as a resource inside the executable file. Maybe the XAML team needs more time to tweak their engine.