I have an ongoing twitter search for topics of interest to me and
my position at Microsoft, so anything with words like "windows
forms", wpf,
windowsclient.net etc. all show up in my
search. Thought that, I often find folks who are struggling with
how to get started in WPF.
I've been meaning to do some beginning WPF tutorials for a bit.
Since the main focus of my job is to help people be successful with
windows client developer technologies, I figured that would be a
good idea. Scott Hanselman once described our job as "starting when
you hit File->New Project in Visual Studio"
The latest tweet conversation went like this (latest post at
top, starter at the bottom)
I show this not to single out Christian, but to show one
of the roadblocks folks new to WPF often hit: templating of
controls.
If you're used to Windows Forms or even ASP.NET, where
purpose-built controls exist for the common scenarios, doing
something as simple as creating a flat image button can be somewhat
daunting.
The reason is controls in other technologies focus on discrete
scenarios, and try to provide properties to handle every scenario
they support. WPF (and Silverlight) controls, however focus on the
model of the behavior of the control, not the look and feel. The
user interface/experience is completely separated from the behavior
and implementation.
Even looking at the diagram, you can see that the xaml-based
approach looks more complex at first glance. However, once you
realize that all you need is markup to completely change the user
interface for the button (not just borders or shape, but everything
about its visual representation) you can see how this approach both
scales better and is generally easier to use long-term.
I'm not making a value judgment as to which approach is better,
but I personally prefer the template model used by Silverlight and
WPF over the purpose-built or partially-templatable, or
templatable-with-many-assumptions models used in other
technologies. I know many of those technologies have made headway
to get away from this model, but they weren't exactly designed with
it in mind.
Each of the button controls in the "Many other Technologies"
section could have completely different implementations. They may
or may not be based on a common base class, and they may or may not
expose similar events or commands. The fact that many do is based
more on a convention than something enforced in code. Maybe the
events are in a slightly different order, or maybe there is some
assumed margin or padding. Maybe you can't hook certain events like
a hover without resorting to Win32 calls, and you almost certainly
can't write a single strongly-typed function that will take in all
variants of buttons and do something with them. If you've ever
spent time getting a complex third-party button to behave just like
a customized built-in button, you know what I'm talking about.
In WPF and Silverlight, everything which has normal button
behavior derives from the ButtonBase class. The most commonly used
derivative of that is the Button class.
There are a number of classes in this tree. Looking at them,
though, you can see that new classes are created only when new
behavior is needed, not when you need a new user interface.
Here is ButtonBase
Here is all that Button adds to ButtonBase.
To throw in a different one, here's ToggleButton, the immediate
ancestor of CheckBox and RadioButton. Note that it adds new
behavior to manage checked and unchecked state.
Button is a Content Control
As you can see in the tree above, Button derives from
ContentControl. That means Button can contain pretty much anything.
We'll focus on content for most of the rest of this post.
Here's a Window with a button that contains just some text:
<Window x:Class="WpfApplication19.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button x:Name="MyCoolButton"
Content="Cool Button"
Width="200"
Height="75" />
</Grid>
</Window>
So, to create a button which looks like a normal button, but
contains an image, how much C#/VB code do we need to write? Answer:
None. Button is a content control, so we just assign it an image as
the content.
<Button x:Name="MyCoolButton"
Width="200"
Height="75">
<Button.Content>
<Image Source="Pete-Brown-Silverlight-in-Action.png" />
</Button.Content>
</Button>
That's the long form, spelling out Button.Content. The more
common form, since Content is, well, the content property, is to
omit that and just nest the content directly in the button
declaration. The end result is exactly the same.
<Button x:Name="MyCoolButton"
Width="200"
Height="75">
<Image Source="Pete-Brown-Silverlight-in-Action.png" />
</Button>
Similarly, we can assign something more complex, like a grid
with an image and multiple rows of text. The content property can
have only one element directly contained within it, so compound
elements must be contained in something like a grid or
StackPanel.
<Button x:Name="MyCoolButton"
Width="200"
Height="75">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="Pete-Brown-Silverlight-in-Action.png"
Margin="5"
Grid.Column="0" />
<StackPanel Grid.Column="1" Margin="5">
<TextBlock Text="Buy My Book!" FontWeight="Bold" />
<TextBlock Text="Pete is writing THE Silverlight 4 book"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Button>
Design-Time in Visual Studio 2010
Runtime
Content Alignment
If you want to change the alignment of the content, you can use
the HorizontalContentAlignment and VerticalContentAlignment
properties inherited from ContentControl.
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Right"
Width="200"
Height="75">
Left, Center and Right are pretty obvious. There is one more
value, Stretch.
Stretch
It looks like the Left-aligned version, but if you look closely
at the size of the content grid and stackpanel, you can see they
now stretch to fill up all available space inside the button, minus
the margins.
Similarly, changing the VerticalContentAlignment will, assuming
your content isn't already taking up all available vertical space,
affect it just like the horizontal settings.
Content can be Almost Anything, even other Controls
Just about anything can be used as content for a content
control, and the button is no exception. In fact, here's a really
crazy assortment of controls included in the button. (Bonus points
if you know the song that starts in the listbox - I'm listening to
about 20 remixes of it as I type this)
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Stretch"
Width="450"
Height="155">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Source="Pete-Brown-Silverlight-in-Action.png"
Margin="5"
Grid.Column="0" />
<StackPanel Grid.Column="1">
<CheckBox Content="Check if you like turtles"
Margin="5" />
<ListBox Margin="5">
<ListBox.ItemsSource>
<sysCollections:ArrayList>
<sys:String>Now how old are you?</sys:String>
<sys:String>Where is your harbor?</sys:String>
<sys:String>Have many things to do</sys:String>
</sysCollections:ArrayList>
</ListBox.ItemsSource>
</ListBox>
<Button Content="Open the Door"
Margin="5" />
<TextBlock Text="Tell me if the sky is blue" />
</StackPanel>
<Image Source="CommodoreLogo_100x100.png"
Stretch="None"
Grid.Column="2" />
</Grid>
</Button>
Sure, that's totally insane for a button, but it
can be done. Imagine what it would take to find
controls that natively support this type of content in another
technology. Heck, finding a button or other control that would
allow you to put in two images seems hard to find.
Changing Colors
Of course, you can change the colors of the button without
re-templating the control. Simply setting the Background to a color
will work:
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Stretch"
Background="Orange"
Width="450"
Height="155">
You can set it to a gradient if you want a highlight effect
similar to the original button. To do that, we break the Background
property out using the property element syntax. This allows us to
specify a complex property value, a LinearGradientBrush in this
instance.
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Stretch"
Width="450"
Height="155">
<Button.Background>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Offset="0" Color="#FFAA0000"/>
<GradientStop Offset="0.49" Color="#FFCC0000"/>
<GradientStop Offset="0.50" Color="#FF990000" />
<GradientStop Offset="1" Color="#FF770000" />
</LinearGradientBrush>
</Button.Background>
Ok, so that is one hideous button, but you get the idea. Note
that that doesn't change the hover effect, though. For that, you'll
need to modify the control template, the topic of our next
post.
Now what if you want a basic flat button without any
re-templating? You can get pretty close by setting colors as well
as padding and making sure your button is sized to the image (or is
at least using the same aspect ratio.
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Stretch"
Padding="0"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Width="146"
Height="183">
<Grid >
<Image Source="Pete-Brown-Silverlight-in-Action.png" />
</Grid>
</Button>
Note that you'll still get the default hover effect, though.
Since there is some transparency in my png, I can see a bit of it.
The hover border will always show unless you have negative content
padding.
And yes, as I mentioned above, changing the padding to a
negative size will obscure the border. It's not the most elegant
solution, but it works, assuming your content is not set to a fixed
size.
<Button x:Name="MyCoolButton"
HorizontalContentAlignment="Stretch"
Padding="-4"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0"
Width="146"
Height="183">
<Grid >
<Image Source="Pete-Brown-Silverlight-in-Action.png" />
</Grid>
</Button>
Be sure to check out Part 2 for information on how to template
the button control, including setting the handling for the various
visual states.