Silverlight Project: WellyTime

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.)

image 

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:

image

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):

image

(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.

VSTO and Visual Studio Standard Edition ... Solution

10 March 2009

In a previous post, I described how annoying it was that Microsoft wanted to keep me from improving their OneNote application, even though I have a legal copy of both it and Visual Studio 2008. I should have mentioned that I understand completely if they want to put the Visual Studio Tools for Office (VSTO) into the Professional Edition and not the Standard Edition. That's their right, even if I think it's stupid. But I was surprised that they would keep people from using the API (which, after all, is just part of a set of DLLs), even if we hadn't purchased the specialized tools. .NET applications, for example, can be written, compiled & run without purchasing any version of Visual Studio. Well, it turns out you can write applications that use the new API without having VSTO; it just takes a little more effort.

Generally, in the .NET world, if you want to use someone's API, you just reference their DLL. In Visual Studio, this is pretty straight-forward: Right-click on the References folder, choose "Add Reference..." and either find it in the list of .NET references or browse for the standalone DLL. In fact, that's exactly what this very useful PDF document about creating a OneNote plug-in and this two-part introduction to using the OneNote API suggest. Unfortunately, neither the .NET reference nor the COM reference appeared among my choices. So I went hunting for the DLLs.

This is where I ran into trouble. The Microsoft Office 2007 API DLLs are officially called "Primary Interop Assemblies" (PIA) because they facilitate the inter-operation between your application and the vendor's application (in this case, the Microsoft Office applications). When you install Office 2007, these assemblies (Microsoft.Office.Interop.OneNote.dll and the like) are installed in the Global Assembly Cache (GAC), but can't be found in Vista. Nor could I find them on the Web. VSTO, I'm told, installs some copies for you to use during development, but that didn't help me since I didn't have VSTO.

After much searching (and cursing), I finally realized that I didn't actually need the DLLs. The APIs are in the GAC, so if I could get my project to reference those, I'd be fine. So I downloaded an open-source project for OneNote (the OneNote DevPal) and opened its project file (.csproj) in notepad. I copied the ItemGroup information pertaining to the OneNote reference out of that file and into my own .csproj file by hand. Then, when I re-opened my own little test add-in, everything was hunky dory.

The XML that I copied looked something like this:

<itemgroup>
<reference include="Office, Version=12.0.0.0">
<private>False</private>
</reference>
<reference include="Microsoft.Office.Interop.OneNote, Version=12.0.0.0">
<private>False</private>
</reference>
<reference include="stdole">
<private>False</private>
</reference>
</itemgroup>

With that little bit of hackery, I was finally able to construct my own test add-in based on Dan Escapa's guide. If you're interested in creating OneNote add-ins, I suggest you start there as well. Keep in mind some errata, however:

* When it says "YOUR GUID", it means a new GUID created specifically for the add-in. Don't use the project GUID, or anything else generated by VS2008. (VS2008 can create GUIDs for you, btw; just look under the Tools menu.)

* You'll have to run Visual Studio 2008 as an administrator (right-click the shortcut, choose "Run as Administrator") if you're in Vista. I think that's due to the modification of the registry that the Setup project performs.

Have fun!

VSTO and Visual Studio Standard Edition ... Complaining

A long, long time ago I read Guy Kawasaki's book The Macintosh Way. It was a decent read, if I remember correctly, but the one item that really stuck with me was an experiment that he did. In an attempt to see how large companies handled potential "evangelists" (people excited about their products), he called a dozen or so companies and said to them, "I like your product. How can I help sell it?" Some companies, like Harley Davidson and Macintosh, were all over it: they had user groups or fan clubs and knew exactly how to get people in touch with others who wanted to spread the word. Other companies (IBM, I think, and Microsoft to be sure) were simply flummoxed by the proposition. This is what came to mind last night as I was trying to figure out how to extend Microsoft OneNote so as to better meet my needs. Sara & I are working on organizing our lives (using Take Back Your Life and Getting Things Done), and as part of that we're trying to figure out what tools will best work for our new organizational structures. Outlook is fine as far as it goes, but I really like OneNote. It's almost, almost exactly what I need. So I figured, hey, I'm a developer. And I know that Microsoft has a OneNote API. I'll just create a little plug-in that completes OneNote for me. And what the heck, I might as well offer it to others, just to show them how useful OneNote can be. Except it's not that easy. It turns out that what you need to write Office plug-ins (and therefore OneNote plug-ins) is something called Visual Studio Tools for Office. So that's cool. I use Visual Studio 2008. Except I use the Standard Edition, and VSTO doesn't come with the Standard Edition. So fine, it's probably worth it to me & I can expense it: I'll just purchase and/or download VSTO as a standalone item. Except it's not available as a standalone. The upshot is: Here I am, someone who wants to enhance the functionality of a Microsoft product and offer it out to the world with the express intention of making OneNote more useful, someone who's willing to do this for free, someone who's willing to spend several hours one night just to figure out how to get started and I can't do it. I have official, paid-for copies of Office 2007 and Visual Studio 2008, and I'd like to use them together. But I get not only no help but actual obstacles thrown in my path from Microsoft. Alright, so I finally figured out how to make it work, even without VSTO. So I'll proceed. My next post will follow up the complaining with an actual solution. But dang: How stupid is that?

Introduction

I've been meaning to create a professional blog for some time, and I realized today that if I don't do it soon, I'll have such a backlog of software development work from projects that I've started that I'll never get around to it. So after playing around with some fancy-schmancy templates, I finally gave up and picked a serviceable Blogger template and ran with it. I suppose "Find a better looking template" has to go on my to-do list now, eh?