Monday, October 27, 2008

VSM Redux – Silverlight 2 RTW

Silverlight's Visual State Manager (VSM) exhibited a couple of problems in Silverlight 2 Beta 2 which I described in my earlier blog post. To summarize, in SL2 Beta 2:
  1. Radio Buttons, Checkboxes, and ListBoxItems ran into problems when trying to animate the same property for Normal, Mouseover, and Checked states.
  2. Animating Brushes was buggy, and caused bad behavior.
So, now that Silverlight 2 has been completed and the final Release To Web (RTW) version is out, do these problems still exist, or have they been solved? Let's find out!

To start with, I upgraded Page Brooks' excellent light bulb demo app to SL 2 RTW. This app demonstrated problem #2 (brush animation bugs) very succinctly. I then added in some code to test out radio buttons to determine if they would still lose their states when animating the same property across multiple state groups. Here's what the test app looks like:


Radio Button Test – Problem #1
To start out, let's take a look at the standard radio button behavior on the bottom radio button:
Normal Mouse Over Checked
 


Notice that the Normal and MouseOver states (which are in the same state group – "Common States") animate the background highlighting, and the Checked state animates the black dot in the center. So, the 2 state groups are each modifying a different property, and mousing over a checked RadioButton still animates the background highlighting.

However, many custom controls are built in a way that animates the same property across multiple state groups. Let's see what happens when we do so. The two circles on the top are custom radio button styles, but all 3 radio buttons are in the same group. The custom radio buttons' backgrounds are grey normally. They should turn yellow on Mouse over, and black on being checked. So, I'll mouse over the first radio button:

 Now I'll select the first radio button:
 So far, so good. However, I'll now mouse over the checked radio button again:

The radio button failed to maintain its checked state, instead applying the mouse over state. When I mouse back out, the checked state does not restore:


So, it seems this problem of animating the same property across multiple VSM state groups still exists in Silverlight 2 RTW.

Brush Animation Test – Problem #2
Moving on, let's test out the brush animation bug. In Silverlight 2 Beta 2, VSM would incorrectly animate changes to the properties (color) of a brush. Every once in a while, the brush color would fail to change. In Page's program, the light bulbs should light up from left to right. A picture of the bug is below:



So, now, let's try it in the upgraded SL2 RTW version of the code:

Moving the mouse very quickly back and forth across the light bulbs produced no errant unlit bulbs at all. It looks like the Brush animation problem has been fixed in the new version of Silverlight!

Friday, September 26, 2008

Return of the Mac (How to squash Silverlight bugs on a Mac using Remote Debugging and Fiddler)


Occasionally, differences between the PC and Mac versions of the Silverlight runtime will crop up, and you'll notice that your code works fine on PCs, but has problems on a Mac. If this is the case, you'll need to remotely debug the problem from a PC with Visual Studio, or use Fiddler to scan network traffic.

Remote Debugging
1) First, you'll want to install the Silverlight Developer runtime for the Mac. This will install a Silverlight Debugging configuration tool – run it.




2) When you run this tool, it will setup your Mac to allow debugging via a specific port, allow you to specify a Remote Debugging password, and generate a ConfigurePCForSilverlightDebugging.exe file for you to run on your PC.
3) Copy that .exe file to your PC and run it.
4) Enter the password you entered on the Mac.



5) Your PC is now ready to debug remotely!
6) Now, launch Visual Studio, and select Debug -> Attach to Process.
7) Select "Silverlight Remote Cross-Platform Debugging" as the Transport.
8) Enter the network name of the Mac as the qualifier.
9) Finally, change the "Attach to" option to "Silverlight".



10) You now see all the processes running on the Mac, and you can attach to the one you want.
11) Optionally, you may want to install Remote Desktop for Mac so that you can use the Mac to terminal into your PC, attach the debugger, and then work in the browser from the Mac. This allows you to manage everything you need to do from one location.

Fiddler
Fiddler runs on the .NET Framework, and is generally only useable on PCs. However, you can route the network traffic from a Mac through a port on a PC in order to run a network trace against a Mac using Fiddler. To do so:
1) Open up Fiddler on your PC, and go to Tools -> Fiddler Options.
2) Check the box for "Allow remote computers to connect".



3) Next, on the Mac, go to System Preferences -> Network -> Advanced, and click the Proxies tab.
4) Use a proxy for Web/HTTP, and enter the network name of the PC on which you're running Fiddler. Enter Port 8888.




Now, when you go to a site on the Mac, the traffic should appear in Fiddler on your PC.

Wednesday, September 24, 2008

The Scroll Button Dilemma

To quote StrongBad, "Scroll buttons never looked so good! Scrollin' up and down like you knew they would..."



Or, perhaps not. We recently encountered a problem when trying to implement scroll buttons in Silverlight 2 Beta 2 that would:
1) Smoothly scoll up and down when being held down.
2) Use VSM (Visual State Manager) for MouseOver and Pressed animations.


Here's how we solved the problem and created scroll buttons truly worthy of some StrongBad adulation:





The problem is that instances of Button controls will never raise MouseLeftButtonUp and MouseLeftButtonDown events. In other words, Button instances only support handling the Click event directly, not the Mouse Up/Down events. We didn't want our users to have to repeatedly click in order to scroll - we wanted the scroll button to just smoothly scroll as long as the mouse button was held down.

This Silverlight.NET forum discussion features an excerpt from a help document that describes the behavior:

"Certain control classes (for example Button) provide control-specific handling for mouse events such as MouseLeftButtonDown. The control-specific handling typically involves handling the event at a class level rather than at the instance level, and marking the MouseLeftButtonDown event data's Handled value as true such that the event cannot be handled by instances of the control class, nor by other elements (at a class or instance level) anywhere further along the event route. In the case of Button, the class design does this so that the event Click can be raised instead."

So, Buttons will swallow the MouseLeftButtonDown/Up events by setting Handled to "true".
Our first thought was to just use an element (other than Button) which doesn't exhibit this behavior. However, using VSM requires using an element that has a customizable control template (derives from the Control class), and it seems as if all the controls that can be used by VSM exhibit the same event swallowing behavior as Buttons.

So, our solution was to build a custom "ScrollButton" class which inherits from Button. The ScrollButton class overrides the OnMouseLeftButtonDown and OnMouseLeftButtonUp handlers, and resets Handled back to "false" after executing the base method. This fairly simple change causes the events to fire properly, allowing our scroll buttons to actually scroll when held down. The code is below:

public partial class ScrollButton : Button{
public ScrollButton()
{
this.MouseLeftButtonDown += new MouseButtonEventHandler(ScrollButton_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseButtonEventHandler(ScrollButton_MouseLeftButtonUp);
}

void ScrollButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.Content = "mouse down";
}

void ScrollButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
this.Content = "mouse up";
}

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
e.Handled = false;
}

protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
e.Handled = false;
}

}

Thursday, September 4, 2008

Don't Fence Me In (I’m just a TextBox)


In Silverlight 2 Beta 2, setting BorderThickness="0" or BorderBrush="Transparent" on a TextBox fails to hide the border around the TextBox. I couldn't get a TextBox I was building to lose its border:


This seemed like a bug to me, but a Silverlight Program Manager explained it this way in a Silverlight.net thread:
"The new visual style for TextBox uses gradients inside a Grid. This default style ignores the border thickness and border brush."

Since this behavior seems to be intentional, a template needs to be used to hide the border. A poster in the thread above suggests using a ScrollViewer in lieu of the TextBox, and it seemed to work for us:


See below for a quick example of how to implement this.
Abridged XAML for TextBox:
<TextBox Style="{StaticResource TextBoxNoBorder}" x:Name="TextSiteURL" />
Template for the TextBoxNoBorder style:
<Style x:Key="TextBoxNoBorder" TargetType="TextBox">
<
Setter Property="Template">
<
Setter.Value>
<
ControlTemplate TargetType="TextBox">
<
Grid x:Name="RootElement">
<
ScrollViewer x:Name="ContentElement" BorderThickness="0" />
</
Grid>
</
ControlTemplate>
</
Setter.Value>
</
Setter>
</
Style>

Wednesday, September 3, 2008

Silverlight/WPF: Returning Image Data from a Converter


Recently, when working with a class that implemented IValueConverter, I encountered some behavior I found unintuitive when returning an image URI value from the Convert() method. My Convert() method was returning a URI string as the Source value for an Image XAML element. This worked on the initial load, but never on reloading the DataContext. I found that in order for the image to change when the DataContext changes, the Converter must return a new BitmapImage as the Source value for the Image XAML element.

For an introduction to IValueConverter, check out this blog post.

My Converter was set up to examine the DataContext of the parent control, and return a specific image depending on settings in the DataContext. The XAML for the image looks like this:

<Image x:Name="frontImage" Source="{Binding Converter={StaticResource ThumbnailImageConverter}}" />
The abridged code for the original converter class appears below:
public class ThumbnailImageConverter : IValueConverter{
#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Show)
{
Show show = (Show)value;
if ((show == null) || (!show.CanPlay))
{
//No video
return "/Images/NoVideo.png";
}
else
{
//Video available
return "/Images/VideoAvailable.png";
}
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion}


However, I found that when the DataContext for the Image's parent control changed, the image would never change over to the correct value. The change only seems to get picked up when the Converter returns a new Bitmap, as shown below:


if ((show == null) || (!show.CanPlay))
{
//No video
return new BitmapImage(new Uri("/Images/NoVideo.png", UriKind.Relative)); }else{
//Video Available
return new BitmapImage(new Uri("/Images/VideoAvailable.png", UriKind.Relative));
}

Tuesday, September 2, 2008

“Shield” Yourself from Animation Problems in Silverlight Visual State Manager


While working on Vertigo's high definition video player for the Democratic National Convention, our team encountered some challenges with Silverlight 2 Beta 2's Visual State Manager. In this post, I'll discuss how we overcame these challenges, and our solution to make working with VSM simple, quick, and easy. 

For an introduction to Visual State Manager, check out Scott Guthrie's Silverlight 2 Beta 2 intro here.

Problem #1: Radio Button and ListBoxItem Selection States
Radio buttons and ListBoxItems both have a "selected" state. The XAML for the VisualStateGroups for a ListBoxItem look like this (the same states are used for Radio buttons):

<vsm:VisualStateManager.VisualStateGroups>
<
vsm:VisualStateGroup x:Name="CommonStates">
<
vsm:VisualState x:Name="Normal" />
<
vsm:VisualState x:Name="MouseOver">
<
Storyboard>
<!-- Animate the MouseOver -->
</Storyboard>
</
vsm:VisualState>
</
vsm:VisualStateGroup>
<
vsm:VisualStateGroup x:Name="SelectionStates">
<
vsm:VisualState x:Name="Selected">
<
Storyboard> <!-- Animate the Selection -->
</Storyboard>
</
vsm:VisualState>
<
vsm:VisualState x:Name="Unselected"/>
</
vsm:VisualStateGroup>
</
vsm:VisualStateManager.VisualStateGroups>


Notice that there are 2 VisualStateGroups – one for "CommonStates", and one for "SelectionStates". Keep in mind that a control must be in one state from each VisualStateGroup at all times. This means that when a ListBoxItem or RadioButton is in the "Selected" state, it must also be in either the Normal/default state or in the MouseOver state:

Possible StatesNormal & Unselected
MouseOver & Unselected
Normal & Selected
MouseOver & Selected


This becomes a problem when you need to define a 3-state setup for your buttons, when the same property has different values in all 3 states. If the "Selected" Visual State modifies a property that is also modified by the "MouseOver" Visual State, a conflict occurs.

For example, let's say we want to modify the text color on a ListBox Item or button. When you select the button, it will appear in the "selected" state:



However, when you mouse out of the button's area, the button will return to the "normal" state, even though the button is also in the "selected" state. Since the button is both "selected" and "normal", and both those states modify the text color, the last state to transition "wins". Your selected button will show both states, with the normal state on top, making the text appear thin and unreadable:



Note that the same problem exists for CheckBoxes, which have a "CheckStates" VisualStateGroup.

Problem #2: Animating Brushes
We noticed that when using a ColorAnimation within VSM to change the color of a brush, strange behavior occurs. Either the animation does not occur, or it occurs on an adjacent control. This only happens on 1 out of every 10 transitions - a representative from Microsoft acknowledges the problem in this Silverlight forum discussion.

To see it for yourself, take a look at this sample application built by Page Brooks to illustrate the problem. Mouse over the dots back and forth - you'll notice states getting stuck. The source code is also available.



Our Solution to Both Problems: Opacity-Only Animations and the "Selected Shield"
To circumvent these problems, we only ever animate opacity. We build a unique element (Grid, Canvas, whatever) for each Visual State, all having an opacity set to 0 other than the element for the "Normal" state. We then simply animate using a DoubleAnimation, changing the opacity on each element.

If our elements include only opaque items, such as images, this would be enough. However, if our elements contain transparent items, we still have a problem. Our selected state will be set to an opacity of 1 along with the MouseOver state, so both will appear to bleed into each other. To solve this, we also include an opaque "Shield" element in the selected state. We set the Z-orders of these elements so that the selected element appears on top of the shield, and the shield element appears on top of the MouseOver and Normal elements. See below for an example of this setup in the XAML.

Of course, having to make multiple copies of the exact same XAML elements is not optimal, and ends up requiring more lines of code. However, it is the only way we have found to totally squash both of these problems.

<ControlTemplate TargetType="ListBoxItem">
<Grid x:Name="LBGrid" Background="Transparent" Cursor="Hand">

<
vsm:VisualStateManager.VisualStateGroups>
<
vsm:VisualStateGroup x:Name="CommonStates">
<
vsm:VisualState x:Name="Normal" />
<
vsm:VisualState x:Name="MouseOver">
<
Storyboard>
<
DoubleAnimation Storyboard.TargetName="canvasNormal" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="0"/>
<
DoubleAnimation Storyboard.TargetName="canvasMouseOver" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="1"/>
</
Storyboard>
</
vsm:VisualState>
</
vsm:VisualStateGroup>
<
vsm:VisualStateGroup x:Name="SelectionStates">
<
vsm:VisualState x:Name="Selected">
<
Storyboard>
<
DoubleAnimation Storyboard.TargetName="canvasNormal" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="0"/>
<
DoubleAnimation Storyboard.TargetName="canvasMouseOver" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="0"/>
<
DoubleAnimation Storyboard.TargetName="canvasSelectedShield" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="1"/>
<
DoubleAnimation Storyboard.TargetName="canvasSelected" Storyboard.TargetProperty="(UIElement.Opacity)" Duration="00:00:00.001" To="1"/>
</
Storyboard>
</
vsm:VisualState>
<
vsm:VisualState x:Name="Unselected"/>
</
vsm:VisualStateGroup>
</
vsm:VisualStateManager.VisualStateGroups>

<
Grid.ColumnDefinitions>
<
ColumnDefinition Width="255"/>
</
Grid.ColumnDefinitions>
<
Grid.RowDefinitions>
<
RowDefinition Height="24"/>
<
RowDefinition Height="3"/>
</
Grid.RowDefinitions>

<Canvas x:Name="canvasMouseOver" Grid.Column="0" Grid.Row="0" Opacity="0">
<
Rectangle Canvas.Left="0" Canvas.Top="0" Stretch="Fill" VerticalAlignment="Stretch" Width="255" Height="24" >

<Rectangle.Fill>
<LinearGradientBrush StartPoint="0.511514,-0.00480887" EndPoint="0.511514,1.0625">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#80FFFFFF" Offset="0"/>
<GradientStop Color="#805B7F9C" Offset="0.34322"/>
<GradientStop Color="#80FFFFFF" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</
Canvas>

<Canvas x:Name="canvasNormal" Grid.Column="0" Grid.Row="0" Background="Transparent" Opacity="1">
<
Rectangle />
</
Canvas>

<
Canvas x:Name="canvasSelectedShield" Grid.Column="0" Grid.Row="0" Background="#ABBDD1" Opacity="0"/>

<
Canvas x:Name="canvasSelected" Grid.Column="0" Grid.Row="0" Opacity="0"> <Rectangle Canvas.Left="0" Canvas.Top="0" Stretch="Fill" VerticalAlignment="Stretch" Width="255" Height="24" >
<
Rectangle.Fill>
<
LinearGradientBrush StartPoint="0.525718,0.0128409" EndPoint="0.525718,1.0513">
<
LinearGradientBrush.GradientStops>
<
GradientStop Color="#80FFFFFF" Offset="0"/>
<
GradientStop Color="#807E9EB8" Offset="0.360731"/>
<
GradientStop Color="#80FDFDFD" Offset="1"/>
</
LinearGradientBrush.GradientStops>
</
LinearGradientBrush>
</
Rectangle.Fill>
</
Rectangle>
</
Canvas>

</Grid></ControlTemplate>

Friday, August 29, 2008

Democratic National Convention: A Resounding Success for Vertigo!


Vertigo's DNC team is sitting in the Denver airport right now after an exciting and exhausting week at the Democratic National Convention. We built the High Definition video website for the convention, and it was a huge success! We had a massive number of people around the world watch the convention live on our site, and what we achieved is an internet first. Never before has a live event of this historical importance been made available in quality that exceeds most HDTV broadcasts.

Additionally, our site broadcast the ENTIRE convention, not just the hour of primetime coverage offered by the TV networks, and the proceedings in their entirety are offered in Video On Demand form on the site.

You can check it out at gallery.demconvention.com.

For more information and pictures from our trip, check out the convention feature page on Vertigo's site here:
http://www.vertigo.com/dnc.aspx



Sunday, August 24, 2008

Vertigo Arrives at the Democratic National Convention!


Over the last couple of months, Vertigo has been building the High Definition, Silverlight-driven video website for the Democratic National Convention. 6 members of our team arrived in Denver yesterday, and we'll be supporting the site throughout the convention, which runs from Monday August 25 to Thursday August 28.

This is an exciting project for Vertigo, as we're breaking new ground. This is one of the biggest events to have ever invested this heavily in live, high definition quality coverage. The site allows Video on Demand as well as a full schedule, and will be available during and after the event. The site will really begin to "light up" this week as the live coverage begins, and a slew of high definition VODs begin to appear in our video gallery. The site can be viewed here:

http://gallery.demconvention.com

We got the opportunity to tour the convention floor at the Pepsi center last night, and I was very impressed by the visual design of the stage, as well as all the effort being put into the event.

Check out the pictures from our trip here.
Some screenshots from the app appear below. Note that the second image is actually a screenshot – not a picture we took!