design: XAML image template-swapping part 2: importing images using Expression Blend
This is the second of three articles on how to create and use vector images from Adobe Illustrator as XAML art in a WPF project. This second article has us laying out the simple bones of a WPF project and importing our images into a Resource Dictionary using Expression Blend 3.0. We learned in the first article how to create images suitable for importing using Adobe Illustrator. The third article shows the template-switching technique that maps images to button states.
let's create a simple WPF project
In Expression Blend 3, create a new WPF project. In the MainWindow.xaml
file, divide the grid into two columns and place a Button into one column
and a CheckBox into the other. Add an empty ContentControl as the
content of the Button. Our Illustrator images will eventually become templates
for this ContentControl that we'll swap out in styles. Finally, bind the button's
IsEnabled property to the IsChecked property of the checkbox. Run the
project and test to make sure the button is disabled when you uncheck the checkbox. Sample code
for this stage is shown below.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ImageStatesArticle.MainWindow"
x:Name="Window"
Title="Image States Article"
Width="300" Height="200">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="buttonImageStates" Grid.Column="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsEnabled="{Binding ElementName=checkEnableButton, Path=IsChecked}">
<ContentControl x:Name="contentImageStates" Width="24" Height="24"/>
</Button>
<CheckBox x:Name="checkEnableButton" IsChecked="True" Grid.Column="1"
HorizontalAlignment="Left" VerticalAlignment="Center">
Enable button
</CheckBox>
</Grid>
</Window>
importing images into a Resource Dictionary
Next, we'll get setup to import our images. Create a new Resource Dictionary in your project
(File | New Item... | Resource Dictionary). Name the Resource
Dictionary and then open it up in Blend, in a split view so that you can see the code. Now
add a new ControlTemplate to the Resource Dictionary, with a Viewbox and a
Canvas within. Here's what the Resource Dictionary XAML code looks like at this stage:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Normal splat image -->
<ControlTemplate x:Key="SplatNormal">
<Viewbox Stretch="Uniform">
<Canvas>
</Canvas>
</Viewbox>
</ControlTemplate>
</ResourceDictionary>
The ControlTemplate will eventually become the Template property of
the Button's ContentControl, which we'll set later in a style. The Viewbox
allows the content within the ControlTemplate to stretch in a uniform fashion, which
is important if you plan to add zoom or other resize logic to your application. The blank
Canvas is exactly that — a blank canvas. We'll fill it in when we import our
Illustrator image.
importing your Illustrator artwork
With the Resource Dictionary visible in split view, click on the SplatNormal item
in the Resources tab. The ControlTemplate will be visible above the code. Here's what
this looks like:
Now we're ready to import. Choose File | Import Adobe Illustrator File...
from the menu and select the normal splat image. If this menu item is disabled, make sure you've got
the split XAML view selected in Blend and have the control template highlighted. Don't ask me
why this has to be this way; I just work here. A bunch of mystery code will be inserted into the
Canvas. Unfortunately, this code isn't immediately useful right out of the box —
we'll need to do some editing. Mine looked something like this (big bewildering Data statements
omitted for brevity):
<ControlTemplate x:Key="SplatNormal">
<Viewbox Stretch="Uniform">
<Canvas>
<Canvas x:Name="asterisk_normal" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="24" Height="24">
<Canvas x:Name="asterisk_normal1" Width="24" Height="24"
Canvas.Left="0" Canvas.Top="0">
<Path Fill="#FFD0D2D3" Stretch="Fill" Width="24" Height="24"
Canvas.Left="0" Canvas.Top="0"
Data="F1M0,24L24,24L24,0L0,0z"/>
<Path Stretch="Fill" Width="22.169" Height="21.213"
Canvas.Left="1.265" Canvas.Top="1.27" Data="BlahBlahBlah...">
<Path.Fill>
<LinearGradientBrush EndPoint="0.688,1.009"
StartPoint="0.345,0.023">
<GradientStop Color="#FF5089BF" Offset="0"/>
<GradientStop Color="#FF5089BF" Offset="0.025"/>
<GradientStop Color="sc#1, 0.041948162, 0.12507914,
0.29663372" Offset="0.30327710771064165"/>
<GradientStop Color="#FF0C004C" Offset="1"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
<Path Fill="#FF262261" Stretch="Fill" Width="22.416"
Height="21.462" Canvas.Left="1.14" Canvas.Top="1.145"
Data="BlahBlahBlah..."/>
</Canvas>
</Canvas>
</Canvas>
</Viewbox>
</ControlTemplate>
whudat?
Let's look over what we've got here. There's our ContentControl and Viewbox,
followed by three separate Canvas controls. The innermost Canvas is recognizable
as the imported Illustrator content by the Top and Left attributes of
0, as well as by the mangled version of the sourcefile's topmost layer name. The
Width and Height of this Canvas are each 24, the same as the
pixel dimensions of the Illustrator drawing. The first Path statement is to our gray
rectangle. You can recognize this by the gray Fill color, by the four points in the
Data attribute, by the Height and Width dimensions of 24,
or simply by the fact that it's the first path within the imported Canvas.
Those of you who are comfortable with Illustrator's Layers palette will immediately see something wrong here: the geniuses at Microsoft decided that the lowest item in the Z-Index needed to be at the top of the XAML code. This is everywhere, throughout every scrap of XAML in the universe, and I don't expect that I'll ever get used to it. Bet you don't either. Take a short break now to yell and scream and break beer bottles against a brick wall or something and get it out of your system. I'll wait.
tweaking time
You'll notice that nothing appears in the split visual display in Blend above the code. We need to
do some hand editing to fix this. Begin by removing the first two Canvas tags, and their
closing tags below. The only remaining Canvas tag should be the one with the
Canvas.Top and Canvas.Left attributes. After you do this, you'll be able to
see the image in Blend.
Next, remove the Path statement that corresponds to the gray rectangle — this is
the topmost (um, lowest z-index, sigh) Path in the import. Highlight this Path
and press the Delete key. The gray background will disappear, and you'll be left with the source image
precisely positioned within the ContentTemplate. Here's what this code looks like after
adjusting indents (again, I've left out the long Data statements):
<!-- Normal splat image -->
<ControlTemplate x:Key="SplatNormal">
<Viewbox Stretch="Uniform">
<Canvas x:Name="asterisk_normal1" Width="24" Height="24"
Canvas.Left="0" Canvas.Top="0">
<Path Stretch="Fill" Width="22.169" Height="21.213"
Canvas.Left="1.265" Canvas.Top="1.27" Data="BlahBlahBlah...">
<Path.Fill>
<LinearGradientBrush EndPoint="0.688,1.009"
StartPoint="0.345,0.023">
<GradientStop Color="#FF5089BF" Offset="0"/>
<GradientStop Color="#FF5089BF" Offset="0.025"/>
<GradientStop Color="sc#1, 0.041948162, 0.12507914,
0.29663372" Offset="0.30327710771064165"/>
<GradientStop Color="#FF0C004C" Offset="1"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
<Path Fill="#FF262261" Stretch="Fill" Width="22.416"
Height="21.462" Canvas.Left="1.14" Canvas.Top="1.145"
Data="BlahBlahBlah..."/>
</Canvas>
</Viewbox>
</ControlTemplate>
add the other image states
So I bet you can tell what's coming next, right? It's time to add another
ContentTemplate, this time for the disabled state of the button. Start
with the same structure as before:
<!-- Disabled splat image -->
<ControlTemplate x:Key="SplatDisabled">
<Viewbox Stretch="Uniform">
<Canvas>
</Canvas>
</Viewbox>
</ControlTemplate>
Now import the Illustrator sourcefile for the Button's disabled state, same as we
did the first image. After importing, remove the first two Canvas statements
and their closing tags, delete the first Path statement that corresponds to
the gray rectangle layer, adjust the indents and save. At this point, when you click the
resources one after the other in the Resources tab, you'll see that they are exactly
the same size, and positioned exactly above each other.
Repeat the import process for the lit state image. We've now imported our vector graphics into a useful structure in our Resource Dictionary. We have normal, disabled, and lit versions of the same image, each precisely positioned at the same point within a Canvas, and each able to be dynamically resized. We'll wire up these images in a style so that they appear in the button at the appropriate times in the last article in this series, XAML image template-swapping part 3: switching images in a style.