11 March 2009
Over my career as a developer there have been a few times when everything about Windows development up and changed on me: such as in the early 90s, when message loops became events; and later, in the early 00s, when MFC became .NET. Each time the change was for the better, but it wasn't easy to make the fundamental change in viewpoint required. I'm getting the same feeling now as I'm working to grok Silverlight and the Windows Presentation Foundation (WPF).
I've been reading about Silverlight and playing around with it ever since settling in Wellington for our sabbatical, but nothing teaches better than an honest-to-goodness project, so I've decided to pick a simple Silverlight application and take it from conception to implementation. I'm also going to describe the process here on this blog; partly because I've found it valuable for me when others do this, but mostly because I believe it enforces the learning process. So here goes.
Project WellyTime
On our Wellington Blog, I have a simple little Google gadget that shows the current time in Wellington, NZ. It works just fine, except for two things: One, it doesn't say anything about being the time in Wellington, I have to put that outside of the control. Two, it has ads. They can be turned off, but still ... I'd rather not have them at all. So I figured a simple Silverlight app that showed the current time in Wellington would be a good starting point. 
The original Google gadget I used on the website basically has the look that I want (see image at right): rounded rectangle behind, large text for the time, smaller text above and below for the date and (in my case) the city name. So my first job was to replicate this look & feel.
Outdated Thinking
Arranging the text is a pretty simple proposition: I knew I wanted either a Grid control or a StackPanel as my container. The Grid adds a tiny bit of overhead (I think), but offers a little more versatility, and I don't think that StackPanel can be the root node. So Grid it was. But how to get the rounded rectangle behind it? That's where I made my first big mistake.
The code that I started with looked something like this:
<UserControl x:Class="WellyTime.Step1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="120"> <Canvas x:Name="MainCanvas" Background="White" Width="200" Height="120"> <Rectangle x:Name="BackgroundRect" Fill="Blue" RadiusX="15" RadiusY="15" Width="200" Height="120" /> <Grid x:Name="MainGrid" ShowGridLines="True" HorizontalAlignment="Center" VerticalAlignment="Center" Width="200" Height="120"> <Grid.RowDefinitions> <RowDefinition Height="Auto" MinHeight="18" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" MinHeight="18" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBlock x:Name="DateText" Text="Wednesday, September 29, 2009" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="0" /> <TextBlock x:Name="TimeText" FontSize="36" Text="12:00 PM" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="1" /> <TextBlock x:Name="CityText" Text="Wellington, New Zealand" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Column="0" Grid.Row="2" /> </Grid> </Canvas> </UserControl>
That works okay, and gives us what you see below, Which is pretty close, considering I’ve not put any gradients on the rectangle fill.
(NOTE: In order to get this size, I had to change the default HTML test page so that the control’s containing DIV had a specific width & height set. Otherwise, the width would be the webpage width.)
But all those Width=”200” Height=”120” attributes set my developer senses tingling. I’d like to have my little Silverlight control fill up the space it’s been given; and the same goes for the Grid within and the background rectangle.
The UserControl that is my application and the Grid are easy: if you leave off the Width and Height attributes, they default to Double.NaN, or “Auto” in XAML, which means “as large as your container”. (See this MDN page for details.)
The Rectangle is another matter, though. Its width and height seem to default to zero, even though they are ostensibly inherited from the same FrameworkElement class that the UserControl and Grid are.
Since I was still thinking old-school, I started by resizing the rectangle whenever I got a SizeChanged event from the Canvas. Again, it worked, but didn’t seem like the best way to go. Then, as I was stumbling around looking for something else, I found someone who’d added a Border to their Grid. Ah ha!
Using the Grid Border
The key to understanding this aspect of the WPF/Silverlight paradigm shift is to think of the design elements (such as background) as part of the controls themselves. I don’t really want a rounded rectangle behind a grid; I want a grid whose background is a rounded rectangle.
I added a Border node within the Grid and after a little fooling around with the fill values, I managed to produce this:
Nice, huh? The beauty is: that’s not a Rectangle. It’s a Grid. But that Grid has the following code within it:
<Border BorderBrush="LightBlue" BorderThickness="2" CornerRadius="15" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3"> <Border.Background> <RadialGradientBrush Center="0.5,0.5" GradientOrigin="0.10,0.25" RadiusX="0.85" RadiusY="1.25" Opacity="1.0" SpreadMethod="Pad" > <GradientStop Color="LightBlue" Offset="0.0"/> <GradientStop Color="Blue" Offset="1.0"/> </RadialGradientBrush> </Border.Background> </Border>
Then I threw in another border (cool!) right underneath that one.
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="15" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" />
That gave me a nice black outline. I love it that you can add borders one after another with the understanding that – like anywhere else in Silverlight – the later one is displayed on top of the previous one. Take that, CSS!
After adding back in the text, setting sizes and whatnot, I ended up with something that looks pretty nice (nicer even than the original I was copying, IMHO):
(NOTE: I changed the sizes a bit on the test HTML page to make this look right. I’m now at 225px wide and 85px high for the wrapping DIV.)
You can find the .xaml and .xaml.cs files for this my WellyTime code directory. That’s just those files for now; I’ll upload the whole project when I’m done.
0 comments:
Post a Comment