WPF Styles

One of the things I planned to do this holiday season was spend some time learning about Windows Presentation Foundation (here is the Microsoft link).

The first thing to did was write a "Hello World" program.  I wanted just a simple window with a text box that said "Hello World".   It was easy - Expression Blend and Visual Studio 2005 worked seamlessly together: it took about 5 minutes to get a basic "Hello World" program running - cool!   It was relatively attractive and I set the background brush of the text box to Null so that it was transparent, giving the appearance of the text floating on the gradient background of the window.

I decided I wanted my little text box to change color when the mouse hovered over it.   Then the WPF learning curve started to kick in.   Ah ah!  XAML styles and triggers are the answer.... 

My first attempt was the following ..... bbzzzzzz..... it didn't work...  mmmmm 

    1:  <Window
    2:      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3:      xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    4:      xml:lang="en-US"
    5:      x:Class="HelloWorld.MainWindow"
    6:      x:Name="TheMainWindow"
    7:      Title="RGR's First WPF App"
    8:      Width="640" Height="480" ResizeMode="CanMinimize" 
    9:      xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
   10:   
   11:      <Window.Resources>
   12:          <Style TargetType="TextBox">
   13:              <Style.Triggers>
   14:              <Trigger Property="IsMouseOver" Value="true">
   15:                  <Setter Property="Background" Value="#FF071D43" />
   16:              </Trigger>
   17:              </Style.Triggers>
   18:          </Style>
   19:      </Window.Resources>
   20:     
   21:      <Window.Background>
   22:          <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
   23:              <GradientStop Color="#FF0E0C0C" Offset="0"/>
   24:              <GradientStop Color="#FF661212" Offset="0.207"/>
   25:          </LinearGradientBrush>
   26:      </Window.Background>
   27:   
   28:      <Grid x:Name="LayoutRoot">
   29:          <Grid.ColumnDefinitions>
   30:              <ColumnDefinition Width="*"/>
   31:          </Grid.ColumnDefinitions>
   32:          <TextBox Margin="8,8,8,0" x:Name="HelloTB" VerticalAlignment="Top" Height="64" 
   33:                   BorderBrush="{x:Null}" FontSize="36"  Foreground="#FF7E6E6E" 
   34:                   IsUndoEnabled="False" Text="TextBox" TextWrapping="NoWrap" ToolTip="Text Box" 
   35:                   BorderThickness="0,0,0,0" IsReadOnly="True" 
   36:                   Background="{x:Null}"/>
   37:      </Grid>
   38:  </Window>

 

After some dorking around, it finally dawned on me what was wrong - styles are hierarchical.   The style trigger was indeed working, it simply wasn't overriding the Background="{x:Null}" attribute of the TextBox as it has a higher precedence than the style in the trigger.  The following example worked nicely:  Note that on line 13, the Null background characteristics is specified in the style, and not directly in the attributes of the TextBox itself.   This allows the background "Setter" in the IsMouseOver trigger to work correctly as it and the default background have the same precedence.

    1:  <Window
    2:      xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3:      xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    4:      xml:lang="en-US"
    5:      x:Class="HelloWorld.MainWindow"
    6:      x:Name="TheMainWindow"
    7:      Title="RGR's First WPF App"
    8:      Width="640" Height="480" ResizeMode="CanMinimize" 
    9:      xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
   10:      
   11:      <Window.Resources>
   12:          <Style TargetType="TextBox">
   13:              <Setter Property="Background" Value="{x:Null}" />
   14:              <Style.Triggers>
   15:              <Trigger Property="IsMouseOver" Value="true">
   16:                  <Setter Property="Background" Value="#FF071D43" />
   17:              </Trigger>
   18:              </Style.Triggers>
   19:          </Style>
   20:      </Window.Resources>
   21:     
   22:      <Window.Background>
   23:          <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
   24:              <GradientStop Color="#FF0E0C0C" Offset="0"/>
   25:              <GradientStop Color="#FF661212" Offset="0.207"/>
   26:          </LinearGradientBrush>
   27:      </Window.Background>
   28:   
   29:      <Grid x:Name="LayoutRoot">
   30:          <Grid.ColumnDefinitions>
   31:              <ColumnDefinition Width="*"/>
   32:          </Grid.ColumnDefinitions>
   33:          <TextBox Margin="8,8,8,0" x:Name="HelloTB" VerticalAlignment="Top" Height="64" 
   34:                   BorderBrush="{x:Null}" FontSize="36"  Foreground="#FF7E6E6E" 
   35:                   IsUndoEnabled="False" Text="TextBox" TextWrapping="NoWrap" ToolTip="Text Box" 
   36:                   BorderThickness="0,0,0,0" IsReadOnly="True" />
   37:      </Grid>
   38:  </Window>

One thing I did find frustrating - especially as someone accustomed to native code development (I can barely spell ".NET") is how easy it is to hork up the XAML so that the resulting executable crashes.  For example, this little code fragment crashings Hello World nicely...

 <Setter Property="Background" Value="x:Null" />

The buggy code compiled just fine in both Visual Studio 2005 and Expression Blend Beta-1.   But when the Hello World program ran, it simple crashed.  Even more bothersome was the fact that the VS2005 debugger was useless.  When the program crashed, it popped up a useless dialog box with the wrong information.  The bug is simple, there are no curly braces "{ }" around the x:Null.  The correct snipit is:

 <Setter Property="Background" Value="{x:Null}" />

This isn't too surprising given that the task of processing the XAML code into a .NET object model is multi-step, happens at run-time and is complex.  Given that XAML, WPF and the related technologies are so new, its amazing that the tools work as well as they do.  The caveat here is that don't expect as robust a development and debugging environment with XAML that we enjoy with native C++, Managed C++ or C#.  It will take some time (Orcas??) but the Dev Div folks will get there!

My next task is to make a program that does something useful.  I have a program that counts the lines of code in various languages (C++, C#, Perl, batch files, assembly language, etc).  I planto build a WPF program that displays a tree map of line counts for the entire Windows Vista code base.....  We'll see how WPF scales with this one.