Welcome to WindowsClient.net | Sign in | Join

Nidonocu - typeof(Tea)

Random discoveries as I play with WPF

Sponsors





  • advertise here

March 2008 - Posts

SizeToContent border bug

This isn't part three of my theme series as I promised, but instead a random discovery which is after all what I subtitled this blog with. :) I'll get back to that in due course!

Instead, while building a dialog box today, I came across a very weird bug which seemed to have apparent cause. Can you see it?

Border Bug

Those with keen eyes will spot a distracting single pixel width black border along the inside edge of the window on the right and bottom sides (click the image to see it a bit clearer at the original size). This only seemed to kick in once I enabled the SizeToContent property and set it to WidthAndHeight.

SizeToContent is something very useful and not something I wanted to disable, it would let my window resize according to the content inside it which means if the user opens that expander, the window would grow to account for the extra space needed. All very WPF.

None of the child controls were causing it either, as you can see, I had not yet started to style the dialog yet.

After some poking around, I pinned down an option that seemed to be a workaround solution, enabling the SnapsToDevicePixels property on the Window:

In Expression Blend

Or in XAML:

<Window x:Class="Nox.Amuse.Dialogs.QuickConnectSetupDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Quick Connection" ResizeMode="NoResize"
    WindowStartupLocation="CenterOwner" x:Name="DialogWindow"
    SizeToContent="WidthAndHeight"
    ShowInTaskbar="False" SnapsToDevicePixels="True">

As far as I can work out, the bug was caused by my window sizing down to my content, but not quite enough (half a pixel or so, which renders as the greyish single pixel-width lines). SnapToDevicePixels does what it says on the tin, it forces the window to clip down to whole pixels. Feel free to comment if you I've not got this explanation quite right!

After applying the property and running again, I just have the regular border again:

All Gone!

Hope this helps others who get caught by this one!

WPF - Themes and Control Libraries - Part 2

Oops.. how did that happen? I said one post a week, so its about time I posted again so that I don't create another abandoned blog! And 5am on a snowy March morning seems about as strange a time as any to try writing some code, so here we go!

Click here for Part 1

How themes work

In using WPF, you will know if you apply a style at the point at where you use the control, it overrides the built in one. This continues when you use themes.

Style Overrides 

When you create a custom control, a style is automatically created in the Generic.xaml resource dictionary. If you don't want a control to be themeable, you can edit this to make it as pretty as you like without it being affected by the user changing their system theme.

Now, if we want a theme aware control, we will need to make a file for each style we are going to support. These are the files that in the above diagram I've simplified to Named Theme Style. No named theme style will ever override another. For example, if you don't include an Luna.Metallic (the Windows XP silver theme), it won't fall back to Luna.NormalColor (the blue and green theme).

There is one exception to this as you can see, the Classic style. If no named theme file is found but a Classic file is, that will be used, not the Generic theme.

I point this out because if your application is loaded on to a system where the user is using an unsupported theme then they will end up with a classic look instead of a more modern look that you have likely provided in your named theme files.

Examples of themes where you might forget to include a style dictionary for them include official visual styles like the Media Center Royale theme or the Zune theme, and unofficial visual styles where the user has patched their uxtheme.dll file to enable the system to run non-Microsoft visual styles. In the latter case you have no idea what the name of theme could be so there is nothing you can do unless you override the entire theme system which is something I will cover how to do in the final part of this tutorial.

Finally of course, Instance styles still remain the most important, by using code such as:

<custom:CustomButton Content="Hello" SecondaryText="WindowsClient.net">
 <custom:CustomButton.Style>
  <Style>
   <!-- Style Definition -->
  </Style>
 </custom:CustomButton.Style>
</custom:CustomButton>

You can override your own styles just the same as if you were overriding styles for the built in controls.

Now that you understand how themes 'fall back' in to one another, we can go ahead and make one!

Creating a named theme

For each named theme, you will need a resource dictionary with a specific name so that the theme system can find it.

To create your resource dictionary, right-click the Themes folder in your Control Library Project. Point to Add and then click Resource Dictionary.

Create Resource Dictionary

What you do next exactly will depend upon if you are running Windows XP or Windows Vista.

If you are running Windows Vista:

Name the file Aero.NormalColor.xaml

If you are running Windows XP:

Name the file Luna.NormalColor.xaml

Then click Add to create the file. I'll refer to from now on as the Named Style file.

Visual Studio will provide you with a template file, but to give us a better starting point, delete the contents of the new file and copy the entire contents of Generic.xaml in to your new Named Style. This will make sure you include the local namespace definition in Generic's ResourceDictionary element.

Now, lets change the look of this style so we can see a difference. In the Named Style file, replace the ControlTemplate element with the following to give it a unique look. You can of course, edit this if you want or come up with something of your own design, just make sure the change is obvious.

<ControlTemplate TargetType="{x:Type local:CustomButton}">
 <Border BorderBrush="White"
         BorderThickness="1">
  <Border BorderBrush="Black"
          BorderThickness="1">
   <Border.Background>
    <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
     <GradientStop Color="DeepSkyBlue" Offset="0"/>
     <GradientStop Color="LightBlue" Offset="1"/>
    </LinearGradientBrush>
   </Border.Background>
   <Border BorderBrush="White"
           BorderThickness="1">
    <Grid>
     <Grid.RowDefinitions>
      <RowDefinition Height="0.5*"/>
      <RowDefinition Height="0.5*"/>
     </Grid.RowDefinitions>
     <ContentPresenter HorizontalAlignment="Center" Grid.Row="0"/>
     <TextBlock HorizontalAlignment="Center" Grid.Row="1"
                Text="{TemplateBinding SecondaryText}"/>
    </Grid>
   </Border>
  </Border>
 </Border>
</ControlTemplate>

Now, you might think that's it, but if you build and run your application right now, you will see that nothing has changed. There is one small piece of code you have to chance to kick in the theme system.

Enabling the theme system

This step is quite important and easy to get confused over when you introduce the complexity of a control library. Setting this wrong can even cause your application to crash if it fails to find a theme, as I discovered when working on the application that prompted me to create this tutorial.

To enable the theme, open your AssemblyInfo.cs file. This is normally hidden under the Properties item and can found by clicking the expander + by the Properties item for your Control Library in the Solution Explorer and then double clicking AssemblyInfo.cs to open the file.

Finding AssemblyInfo

Once inside that file, locate the following section of code, it is normally around line 34:

[assembly: ThemeInfo(
    ResourceDictionaryLocation.None, //where theme specific ...
    //(used if a resource is not found in the page, 
    // or application resource dictionaries)
    ResourceDictionaryLocation.SourceAssembly //where the generic ...
    //(used if a resource is not found in the page, 
    // app, or any theme specific resource dictionaries)
)]

You need to change one value here, the first ResourceDictionaryLocation parameter, to SourceAssembly. If you like, you can strip out the comments too and leave the line now reading as:

[assembly: ThemeInfo(
    ResourceDictionaryLocation.SourceAssembly,
    ResourceDictionaryLocation.SourceAssembly
)]

I should note that making these changes to your .exe's AssemblyInfo.cs file instead of your .dll's, is the thing that in my case caused the crash. So I recommend keeping either all your custom controls in the .exe or all in external .dll assemblies.

Now before you build or run your application, make sure your system theme is set to the one you coded for (on Windows Vista, Aero.NormalColor is used regardless of if you running with or without glass). If it is set correctly, then running the application should produce this:

The themed control

Now, just to give it a test, close the application and repeat the steps above but this time, create a new resource dictionary called Classic.xaml in the Themes folder.

Change the appearance of the control to give it a suitable look (the source for my Classic.xaml can be found in the zip at the end of this post). Then change your system theme to Windows Classic or Windows Standard.

To change your theme under Windows XP:

  1. Right-click the desktop.
  2. Click Display Properties.
  3. Click the appearance tab.
  4. Select Windows Classic style from the Windows and Buttons combo box.
  5. Select a colour scheme if you wish.
  6. Click OK.

To change your theme under Windows Vista:

  1. Right-click the desktop.
  2. Click Personalize.
  3. Click Window Color and Appearance.
  4. If you are currently running Aero glass, click the Open classic apperance and properties link.
  5. Select Windows Standard or Windows Classic from the Color schemes list.
  6. Click OK.

If all goes well, when you run your application, you'll see something like this:

Classic

Now, to see the theme change occur, leave the application running and reset your visual style back to either Aero or Luna Blue. A few moments after your system theme changes, your application should automatically update its own appearance.

 

In the final step, I'll look in to how to force the theme system to select a different theme to deal with the edge cases of unsupported themes. I'll also look in to accessing the current Visual Style information through code so you can make manual adjustments to your application when using the theme system alone isn't convenient.

Themed Application Solution (Part Two)

Zip File - 26KB

Page view counter