Nerdy tidbits from my life as a software engineer

Wednesday, May 13, 2009

Styling a Window Background With a Logo

I finally have a chance to get working with the WPF again.  It’s been a long time, and it’s funny how quickly you both forget and remember things.  A lot of time all I need is a quick look at a reference book and my memory kicks in.  And then there are those things that you wonder how you ever understood in the first place.

I had a friend of mine whip up a simple logo / icon for my current project.  It’s super slick looking, and I want to get it into whatever windows and dialogs and webpages that I can.  Nice UI’s make your work look professional, and if there’s one thing I’ve learned about software, it’s that first impressions make a lasting impact.  All it takes are a few wiz-bang, flashy effects and your customers are instantly impressed.  Of course, you still need to deliver…but that’s another issue.

So I had a great idea to use the logo as the background for all of my windows by using some styles and a VisualBrush.  It’s funny how a seemingly simple project could wrack your brain for a few hours.  The main problem was that, for some reason, there is no trivial way to set the filler background color for a VisualBrush, and so it defaults to black.  So say, for example, that your logo .xaml resource is ~150 x ~76.  What you want is for the visual to be drawn in the center of the window, uniform scaled (so that there’s no distortion), with the rest of the background color set to whatever other color you want.  Since it’s very unlikely that your windows will be sized to exactly fit the 150 x 76 dimensions of the logo, there will inevitably be some dead space in your window.  And what you can’t have is for the background color of the logo to be different from the background color of that dead space.

The other alternative is to scale your logo to either Fill or UniformToFill.  But neither of those options are ideal.  One distorts the logo, and the other will end up clipping much of it off screen.  So you end up having to do some canvas-within-a-canvas scaling magic to get it working properly:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <!-- The logo was exported into .xaml from Adobe Illustrator -->
    <!-- It is defined in a ResourceDictionary as a Canvas with x:Key="Logo" -->
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="..\ResourceDictionaries\Logo.xaml" />
    </ResourceDictionary.MergedDictionaries>

    <Style x:Key="WindowStyle"
           TargetType="{x:Type Window}">
        <Setter Property="Background">
            <Setter.Value>
                <VisualBrush Stretch="UniformToFill">
                    <VisualBrush.Visual>
                        <Canvas Width="100"
                                Height="100" 
                                Background="White">
                            <Canvas Canvas.Left="2"
                                    Canvas.Top="2"
                                    Width="96"
                                    Height="96">
                                <Canvas.Background>
                                    <VisualBrush Visual="{StaticResource Logo}"
                                                 Stretch="Uniform"
                                                 Opacity=".25" />
                                </Canvas.Background>
                            </Canvas>
                        </Canvas>
                    </VisualBrush.Visual>
                </VisualBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Icon"
                Value="..\Logo.ico" />
    </Style>

</ResourceDictionary>

This looks trivial, but the trick was to realize that the logo’s canvas needed to be placed inside another canvas that was UniformToFill.  Any other value and the logo ends up distorted.  For instance, if the outer VisualBrush is set to Fill, then the inner logo ends up being stretched even though it’s set to Uniform.  Setting it to None or Uniform doesn’t work either, because the result is black-colored dead-space.  But setting the outer to UniformToFill causes it to expand to cover the entire background of the window, while the inner canvas’ content is resized to fit the space it’s given.

There may be a better way to do this, I don’t know.  But for now the logo is looking pretty slick.  So I’m going to keep it this way.

0 comments: