Skip navigation

design: XAML image template-swapping part 2: importing images using Expression Blend

November 29, 2009 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
Screenshot of our simple WPF test 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
Creating a new 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.

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:

Control Template structure for importing an image into XAML.

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>

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.

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>

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.

D
D