More Xamliffic Stuff
April 6, 2006
New York City
I know that only programmers read this blog, so I speak freely.
We programmers, as you know, have malformed brains. Most of the time we tend to think like engineers, but if you give a regular engineer a new tool, the engineer will say "Thank you for this interesting tool. I will keep it in mind if a problem arises that seems to require it."
A programmer, on the other hand, given a tool like XAML, starts to think: "This is very cool. I wonder what I can do with this in the absence of all other tools. What it would be like to live on Planet XAML? How would I move around? What would I eat? How would I procreate? And if the only tool I have is XAML, what do the problems look like?"
I wanted to create the image shown above. The shadow TextBlock has a ScaleTransform and SkewTransform applied to it, but to get the baseline of the shadow to align with the baseline of the foreground text, you need to set the CenterY properties of the two transforms equal to the distance between the untransformed top of the text and the baseline.
What is that number? You could figure it out empirically, of course, but that would only work for one particular FontFamily and font size. If you were coding in C#, you'd calculate the offset by mulitplying the Baseline property of the FontFamily object by the font size. But we all know you can't do calculations in XAML. XAML isn't code. It's markup, and it's inherently limited in that way.
But wait a minute: We're doing calculations in XAML all the time. Every transform is a series of multiplications and additions. Might it be possible to define a TransformGroup as a resource, feed in the numbers, and pull out a calculated value?
Yes, it is possible, and that's how the XamlShadow.xaml program is able to calculate a baseline offset for any arbitrary FontFamily and font size. The program is listed here:
-
<!-- =============================================
XamlShadow.xaml (c) 2006 by Charles Petzold
============================================= -->
<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib" >
<Canvas.Resources>
<FontFamily x:Key="fntfam">
Times New Roman
</FontFamily>
<s:Double x:Key="fntsize">
144
</s:Double>
<!-- Multiply font size by FontFamily.Baseline.
Result is stored in xform.Value.M11. -->
<TransformGroup x:Key="xform">
<ScaleTransform ScaleX="{Binding Source={StaticResource fntfam},
Path=Baseline}" />
<ScaleTransform ScaleX="{StaticResource fntsize}" />
</TransformGroup>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="{StaticResource fntfam}" />
<Setter Property="FontSize" Value="{StaticResource fntsize}" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Text" Value="XAML" />
<Setter Property="Canvas.Left" Value="96" />
<Setter Property="Canvas.Top" Value="192" />
</Style>
</Canvas.Resources>
<!-- Shadow Text -->
<TextBlock Foreground="DarkGray">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleY="3"
CenterY="{Binding Source={StaticResource xform},
Path=Value.M11}" />
<SkewTransform AngleX="-45"
CenterY="{Binding Source={StaticResource xform},
Path=Value.M11}"/>
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
<!-- Foreground Text -->
<TextBlock />
</Canvas>
The TransformGroup resource contains two ScaleTransform objects with their ScaleX properties set. One is bound to the Basline property of the FontFamily resource. The other accesses the Double resource with the desired font size. The product is accessed through the "xform" key and Value.M11 path, which the two shadow transforms use to set their CenterY properties.
My name is Charles, and I am a Xamlholic.