Monday 30 July 2007

Silverlight 1.1 Alpha Refresh "Catastrophic Failure"

Microsoft have recently released the second beta of Visual Studio 2008. Along with this there are new "refreshes" of Silverlight 1.1 Alpha and Blend 2. There is a new (and much larger) Silverlight 1.1 SDK, and you will need to also download the new Sliverlight tools to develop projects in Visual Studio 2008. Links to all the downloads are provided here.

I have been updating my SilverNibbles application to run against the new 1.1 Alpha, which was fairly straight-forward. I just needed to replace the Silverlight.js and update the createSilverlight method slightly and everything ran as expected.

There was however one nasty bug resulting in an exception with "Catastrophic Failure" as its error message!

The problem was with the hack I was using (along with many other Silverlight users) to create a timer. Previously, I could simply create a storyboard with a double animation using the following XAML:

<Canvas.Resources>
<Storyboard x:Name="timer">
<DoubleAnimation Duration="00:00:0.08" />
</Storyboard>
</Canvas.Resources>

Unfortunately, when you call timer.Begin() in the new Alpha refresh you will get the "catastrophic failure" exception. The problem is that the DoubleAnimation needs a name and a target. This is easily fixed by creating a hidden rectangle for it to control:

<Canvas.Resources>
   <Storyboard x:Name="timer">
      <DoubleAnimation x:Name="animation" 
         Duration="00:00:0.08"   
         Storyboard.TargetName="InvisibleRect" 
         Storyboard.TargetProperty="Width" />
   </Storyboard>
</Canvas.Resources>
<Rectangle Visibility="Collapsed" x:Name="InvisibleRect" />

Tuesday 10 July 2007

Creating a Custom WPF Button Template in XAML

In this post I will demonstrate how to create a custom template for a WPF button using XAML. In particular we will look at being able to have complete control over all the visual states, including disabled, mouse over, mouse down and even the appearance of the focus rectangle.

Stage 1: Creating a style

The first thing to do is to create a Style which sets the properties of the Button that we want to customize. We will put this in the Page.Resources section of our XAML file.

<Style x:Key="OrangeButton" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="FontFamily" Value="Verdana"/>
<Setter Property="FontSize" Value="11px"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource MyFocusVisual}" />
<Setter Property="Background" >
   <Setter.Value>
       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
           <GradientStop Color="#FFFFD190" Offset="0.2"/>
           <GradientStop Color="Orange" Offset="0.85"/>
           <GradientStop Color="#FFFFD190" Offset="1"/>
       </LinearGradientBrush>
   </Setter.Value>
</Setter>

This is all fairly straightforward stuff - I am just setting the font and background gradient. The only complicated bit is that I am overriding the focus rectangle drawing, as I want a smaller rectangle than the one that gets drawn by default. So I need another style in my Page.Resources section:

<Style x:Key="MyFocusVisual">
     <Setter Property="Control.Template">
       <Setter.Value>
         <ControlTemplate TargetType="{x:Type Control}">
             <Grid Margin="3 2">
               <Rectangle Name="r1" StrokeThickness="1" Stroke="Black" StrokeDashArray="2 2"/>
               <Border Name="border" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding ActualHeight}"  CornerRadius="2" BorderThickness="1" />
             </Grid>
         </ControlTemplate>
       </Setter.Value>
     </Setter>
   </Style>

Stage 2: Creating a template

We would already be finished if I just wanted the normal appearance of my button to be changed. But I want complete control - including the appearance in mouse over, mouse down and disabled situations. So a template allows us to completely control what visual elements constitute our control.

Again this goes in Page.Resources. The first part is shown here:

<Setter Property="Template">
   <Setter.Value>
       <ControlTemplate TargetType="Button">
           <Border Name="border"
               BorderThickness="1"
               Padding="4,2"
               BorderBrush="DarkGray"
               CornerRadius="3"
               Background="{TemplateBinding Background}">
               <Grid >
               <ContentPresenter HorizontalAlignment="Center"
                              VerticalAlignment="Center" Name="contentShadow"
                   Style="{StaticResource ShadowStyle}">
                   <ContentPresenter.RenderTransform>
                       <TranslateTransform X="1.0" Y="1.0" />
                   </ContentPresenter.RenderTransform>
               </ContentPresenter>
               <ContentPresenter HorizontalAlignment="Center"
                           VerticalAlignment="Center" Name="content"/>
               </Grid>
           </Border>

Here you can see I have set up a simple border which gives my button rounded corners, a single pixel gray border, and uses the fill from the control's Background property. This means that by default it will use the orange gradient specified in my style, but that users can override this for their own Background.

To draw the content (usually text), we use a ContentPresenter control. I am trying to do something clever here, which is create a bevelled effect on the text by drawing it again in gray. This works fine on text, but for some reason doesn't do anything when the Content is a Shape. I'm not sure why that is. ShadowStyle is defined as follows:

<Style x:Key="ShadowStyle">
   <Setter Property="Control.Foreground" Value="LightGray" />
</Style>

Stage 3: Applying some triggers

Now we need to add some triggers to our Button template so that we can change its appearance on various events.

First is mouse over. When IsMouseOver becomes true, we change the colour of the border and the text colour to blue. Unfortunately, setting the Foreground property does nothing if the content is a shape.

<ControlTemplate.Triggers>
  <Trigger Property="IsMouseOver" Value="True">
     <Setter TargetName="border" Property="BorderBrush" Value="#FF4788c8" />
     <Setter Property="Foreground" Value="#FF4788c8" />
  </Trigger>

Next is mouse down. When IsPressed becomes true, we modify the background gradient so it looks like the button has gone 'down'. I also move the text down one pixel.

<Trigger Property="IsPressed" Value="True">                   
   <Setter Property="Background" >
   <Setter.Value>
       <LinearGradientBrush StartPoint="0,0" EndPoint="0,1" >
           <GradientStop Color="#FFFFD190" Offset="0.35"/>
           <GradientStop Color="Orange" Offset="0.95"/>
           <GradientStop Color="#FFFFD190" Offset="1"/>
       </LinearGradientBrush>
   </Setter.Value>
   </Setter>
   <Setter TargetName="content" Property="RenderTransform" >
   <Setter.Value>
       <TranslateTransform Y="1.0" />
   </Setter.Value>
   </Setter>
</Trigger>

Third, we draw a dark gray border when a button is focussed or if it is the default button (the button that will be clicked when you press enter).

<Trigger Property="IsDefaulted" Value="True">
   <Setter TargetName="border" Property="BorderBrush" Value="#FF282828" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
   <Setter TargetName="border" Property="BorderBrush" Value="#FF282828" />
</Trigger>

Finally, the when the button is disabled, we gray out the text and wash out the background a little by reducing its opacity.

<Trigger Property="IsEnabled" Value="False">
       <Setter TargetName="border" Property="Opacity" Value="0.7" />
       <Setter Property="Foreground" Value="Gray" />
   </Trigger>
</ControlTemplate.Triggers>

Stage 4: Using the control

Now we are ready to use our control. All we have to do is set the Style property of our button. We can override any of the settings such as font size if we like. The button will automatically resize itself to fit the content.

<StackPanel HorizontalAlignment="Center">
<Button Style="{StaticResource OrangeButton}">Hello</Button>
<Button Style="{StaticResource OrangeButton}">World</Button>
<Button Style="{StaticResource OrangeButton}" FontSize="20">Big Button</Button>
<Button Style="{StaticResource OrangeButton}" IsDefault="True">Default</Button>
<Button Style="{StaticResource OrangeButton}" IsEnabled="False">Disabled</Button>
<Button Style="{StaticResource OrangeButton}" Width="70" Height="30">70 x 30</Button>
<TextBox />
<Button Style="{StaticResource OrangeButton}" Width="30" Height="30">
<Path Fill="Black" Data="M 3,3 l 9,9 l -9,9 Z" />
</Button>
</StackPanel>

Here's what it looks like:

image

Download an example XAML file here.

Monday 9 July 2007

XAML Gel Buttons in WPF and Silverlight

I found a nice tutorial on how to create gel buttons using Inkscape, and converted the technique to XAML.

The basic idea is that you create three rounded rectangles. The main one goes from a dark colour at the top to a bright colour at the bottom. In this case, dark green to bright green.

Then there is a transparent highlight rectangle, which goes from white at the top to transparent black at the bottom. This rectangle is marginally smaller than the main rectangle, which gives us a small border. You can make it smaller using a ScaleTransform, but I found it easier to simply modify the size and corner radius.

Finally there is a drop shadow which is a transparent black rectangle (so it appears grey on a white background). Unfortunately, because Silverlight does not support the BitmapEffect property from WPF, we cannot blur this rectangle, so the effect isn't quite as nice as it could be.

Here's the code:

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  Width="400" Height="200" Background="White">

<!-- drop shadow - needs to be blurred for correct effect -->
<Rectangle Fill="#80000000" Width="200" Height="50" Canvas.Top="52" Canvas.Left="52" RadiusX="15" RadiusY="15">
</Rectangle>


<!-- main rect -->
<Rectangle Width="200" Height="50" Canvas.Top="50" Canvas.Left="50" RadiusX="15" RadiusY="15">
<Rectangle.Fill>
   <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
     <GradientStop Offset="0" Color="#006700"/>
     <GradientStop Offset="1" Color="#00ef00"/>
   </LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>

<!-- transparent rect -->
<Rectangle Width="198" Height="48" Canvas.Top="51" Canvas.Left="51" RadiusX="14" RadiusY="14">
<Rectangle.Fill>
   <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
     <GradientStop Offset="0" Color="#FFFFFFFF"/>
     <GradientStop Offset="1" Color="#00000000"/>
   </LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Canvas>

Here's what it looks like in Silverlight:

silverlight-gel-button

Very nice! But now look what exactly the same XAML looks like in WPF:

wpf-gel-button

Yuck! I spent a while trying to work out how come the same XAML rendered differently in Silverlight and WPF. I eventually found that if I changed the bottom gradient stop in the WPF XAML to transparent white instead of transparent black, I got the same appearance as Silverlight. In fact, it looks to me like it may be the Silverlight rendering that is at fault here rather than WPF.

Anyway, if we combine this information with the blurring of the drop shadow in WPF, we can get a nice gel button in WPF as well.

<Rectangle.BitmapEffect>
<BlurBitmapEffect Radius="3" />
</Rectangle.BitmapEffect>

Here's what it looks like:

wpf-gel-button-drop-shadow

Thursday 5 July 2007

SubSonic GridView and ObjectDataSource

I have been experimenting with using SubSonic to convert my commentaries web page from PHP to ASP.NET. This is a very simple web page allows you to look at a list of books, sort them and filter them - nothing complicated. And Subsonic offers an automatic DAL layer which requires you simply to point it at your database and let it create the classes for you.

So one of the first tasks I approached was to work out how to get a asp:GridView to work with my SubSonic data objects. My first approach was to do it in code, with the following code in my Page_Load event:

CommentaryController c = new CommentaryController();
CommentaryCollection cc = c.FetchAll();            
gridCommentaries.DataSource = cc;
gridCommentaries.DataBind();

This worked nicely, but I wanted to know if there was a better way using the asp:ObjectDataSource. The SubSonic documentation is still a little sparse in this area, but I eventually worked out the syntax. You use the auto-generated "Controller" object that SubSonic has made for your table:

<asp:ObjectDataSource ID="odsCommentaries" runat="server" 
TypeName="CommentariesDB.CommentaryController"
SelectMethod="FetchAll" />

Simple when you know how! Now you just need to set the DataSourceID of the grid. Paging can be also turned on, but sorting doesn't. I'll perhaps post again when I have gone a bit deeper.

Wednesday 4 July 2007

ASPNETDB.MDF?

One of the first things I tried to get my head around while learning ASP.NET 2.0 recently was the user and membership system. Its a nice idea - Visual Studio can go off and create a database for you with all the tables ready to go. It certainly saves a lot of design and implementation time by providing a solution for a common problem.

The only strange thing is where it puts this data. As far as I can tell, it never asks you where you want to store the data. It simply creates a new SQL Express file called ASPNETDB.MDF. In fact, it uses a connection string that is hidden away in your machine.config file, which I found quite confusing at first, as I couldn't work out how my web application knew how to connect to this database.

The trouble with this is obvious. If you already have a database, then you now have two databases. This not only is a problem for those whose web hosting provider charges them for multiple databases, but more seriously doesn't allow you to have a foreign key in one of your data tables pointing at the user table.

Now you might say that I am making a fuss about nothing. Surely I can just move these tables into my existing database. And I probably will. But I noticed that the SubSonic starter site has its data still separated into two databases. Why is this? The CMS database simply stores "user name" and has no hard link to the ASPNETDB database. Of course, you could write some code to look up user details (such as display name, email etc) from the user name, but you have lost the ability to get all your data with one request. What's worse, if a user is allowed to change their user name, all relationships will be broken.

So my question is, why are people doing this? Is there any benefit to having the databases separate? Or is it just because that's what Visual Studio does by default and it is a pain to change?

(not that I'm expecting an answer. I don't think this blog actually has any readers yet!)