Failures in .NET RIA Services are silent by default
19 March 09 09:57 PM | keith.jones | 1 Comments   

Due to the async nature of the .NET RIA Services programming model, exceptions and errors during load and submits aren't thrown on the client. Instead, the errors are exposed through an Error property which can be found in the event args instances passed in to handlers for the Loaded and Submitted events.

So, the first thing I do whenever I create a new DomainContext instance on the client is add a simple handler for the Loaded event. If I am going to be doing submits, then I add a handler for the Submitted event.

For example,

myDomainContext.Loaded += delegate(object sender, LoadedDataEventArgs e)
{
    if (e.Error != null)
    {
        throw e.Error;
    }
};
myDomainContext.Submitted +=
delegate(object sender, SubmittedChangesEventArgs e)
{
    if (e.Error != null)
    {
        throw e.Error;
    }
};

Later on, I might to do more complex things on those event handlers, but at beginning using anonymous methods are just fine.

Filed under: , , ,
Building a dual thumb slider for Silverlight 2 Beta 1
09 April 08 02:06 AM | keith.jones | 1 Comments   

 

hosted by Silverlight Streaming - the source is available as an attachment at the bottom of this post.

The Slider control that shipped with the Silverlight 2 Beta 1 SDK only supports picking a single value. Back at Mix08 in March I threw together a control that allows selecting a range using two thumbs, but I haven't had a chance to talk about it until now. So, without further ado let me introduce the RangeFinder control.

 

 

[TemplatePart(Name = RangeFinder.RootElementName, Type = typeof(FrameworkElement))]

[TemplatePart(Name = RangeFinder.TrackGridElementName, Type = typeof(Grid))]

[TemplatePart(Name = RangeFinder.LeftTrackElementName, Type = typeof(Rectangle))]

[TemplatePart(Name = RangeFinder.RightTrackElementName, Type = typeof(Rectangle))]

[TemplatePart(Name = RangeFinder.LeftGridSplitterElementName, Type = typeof(GridSplitter))]

[TemplatePart(Name = RangeFinder.RightGridSplitterElementName, Type = typeof(GridSplitter))]

public class RangeFinder : RangeBase

{

    /// <summary>

    /// Raised when the UpperValue dependency property changes

    /// </summary>

    public event RoutedPropertyChangedEventHandler<double> UpperValueChanged;

 

    /// <summary>

    /// Constructor for the RangeFinder class

    /// </summary>

    public RangeFinder() {}

 

    /// <summary>

    /// Gets/sets the UpperValue dependency property for the upper value of the selected range

    /// </summary>

    public double UpperValue {get; set;}

 

    /// <summary>

    /// Gets/sets the ThumbWidth dependency property for width of the individual thumbs

    /// </summary>

    public double ThumbWidth {get; set;}

 

    /// <summary>

    /// Gets/sets the TrackMargin dependency property for the distance from the edges of the control that track should be displayed

    /// </summary>

    public Thickness TrackMargin {get; set;}

 

    /// <summary>

    /// Gets/sets the Background dependency property for the background of the control

    /// </summary>

    public Brush Background {get; set;}

 

    /// <summary>

    /// Gets/sets the Foreground dependency property for the foreground of the control

    /// </summary>

    public Brush Foreground {get; set;}

 

    /// <summary>

    /// Gets/sets the MinimumRange dependency property for the minimum allowable difference

    /// between the Value and UpperValue properties

    /// </summary>

    public double MinimumRange  {get; set;}

}

 

 

The first decision to make was whether or not I could extend the existing Slider control. I think I could have restyled the thumb by inserting a custom control that would have allowed me to expand it, but that seemed really complex. Instead, I chose to inherit directly from RangeBase and implement the UI of the control from scratch.

 

The Slider class uses a grid with three columns to lay out the UI. Moving the thumb actually changes the dimensions of the first and third columns. When the size of the first column changes, a rectangle in that column invokes a SizeChanged event handler which updates the Value based on the new size.

 

I thought that was a pretty slick approach so I borrowed the concept for RangeFinder. I had a problem though; I needed two independent values. Instead of creating a second thumb and trying to coordinate it with the first, I decided to use a control whose sole purpose in life is resizing grids - the GridSplitter.

 

Here is the general concept:

  • A three-column Grid
  • Two Rectangles, one filling the first column and the other filling the third.
  • Two GridSplitters, one aligned-right in the first column and the other aligned-left in the third.
  • Dragging one of the GridSplitters resizes the adjacent columns causing a rectangle in one of the columns to invoke its SizeChanged event handler which updates the Value or UpperValue property, as appropriate.
  • There is a little math behind the scenes to convert the value range (e.g. 1 to 100) to visual coordinates (e.g. 0 to 50 pixels).

 

Creating a default RangeFinder

 

Creating an instance of the control on a Silverlight page is very easy. The first RangeFinder shown in the example above just sticks with default colors and a Minimum of 0 and a Maximum of 1.

 

<local:RangeFinder x:Name="rangeFinder0" />

 

 

Seeing the RangeFinder's values

 

The RangeFinder isn't very fun though if you can't see what the Value and UpperValue properties actually are. Silverlight 2 Beta 1 doesn't dynamically update any UI data bound to dependency properties so you need to create a little Data Transfer Object (DTO) to fill the gap. The ValueDataObject class in the attached project serves this purpose.

 

/// <summary>

/// Data transport object for binding the UI to the actual values selected

/// </summary>

/// <remarks>

/// When dependency properties change in Silverlight 2 Beta 1, they do not

/// update the targets of any data binding made to those properties. One

/// way to do this is to use a data transport object that implements the

/// INotifyPropertyChanged interface as the source of the bindings as

/// demonstrated by this ValueDataObject class.

/// </remarks>

public class ValueDataObject : INotifyPropertyChanged

{

    /// <summary>

    /// Implementation of the INotifyPropertyChanged.PropertyChanged event

    /// </summary>

    public event PropertyChangedEventHandler PropertyChanged;

 

    /// <summary>

    /// Constructor for the ValueDataObject class

    /// </summary>

    /// <param name="instance">RangeFinder object to associate this instance with</param>

    public ValueDataObject(RangeFinder instance, string valueFormat) {}

 

    /// <summary>

    /// Gets the Value property of the associated RangeFinder object

    /// </summary>

    public Double Value {get; }

 

    /// <summary>

    /// Gets a formatted string for the Value property of the associated RangeFinder object

    /// </summary>

    public string FormattedValue {get; }

 

    /// <summary>

    /// Gets the UpperValue property of the associated RangeFinder object

    /// </summary>

    public Double UpperValue {get; }

 

    /// <summary>

    /// Gets a formatted string for the UpperValue property of the associated RangeFinder object

    /// </summary>

    public string FormattedUpperValue {get; }

 

    /// <summary>

    /// Gets/sets the string format to be used when formatting the Value and UpperValue properties

    /// </summary>

    public string ValueFormat { get; set; }

}

 

 

Now, you want to set up the UI to be able to show the values. I used two TextBlocks in the second RangeFinder in the example above. Note that the example code binds the Text property of the TextBlocks to the FormattedValue and FormattedUpperValue properties of ValueDataObject. This allows it to change the formatting when converting the value to a string.

 

 

<Grid x:Name="customRangeFinderContainer" VerticalAlignment="Center">

    <Grid.ColumnDefinitions>

        <ColumnDefinition />

        <ColumnDefinition Width="200"/>

        <ColumnDefinition />

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>

        <RowDefinition Height="20"/>

    </Grid.RowDefinitions>

    <TextBlock Grid.Column="0" Style="{StaticResource valueTextBlockStyle}" Text="{Binding FormattedValue, Mode=OneWay}" />

    <local:RangeFinder x:Name="rangeFinder1" Grid.Column="1"

                       Minimum="0" Maximum="5.5" Value=".5" UpperValue="5.0" MinimumRange="1.5"

                       Background="DarkGray" Foreground="Brown" />

    <TextBlock Grid.Column="2"  Style="{StaticResource valueTextBlockStyle}" Text="{Binding FormattedUpperValue, Mode=OneWay}" />

</Grid>

 

 

It was very easy to change the default Background and Foreground colors, the Minimum and Maximum properties, and the initial Value and UpperValue properties. It is also easy to set a minimum range.

 

One last piece is needed to hook it all up. You need to set the DataContext of some parent UI element to be an instance of ValueDataObject. In the example code this is done in the code-behind for Page.xaml.

 

protected override void OnApplyTemplate()

{

    base.OnApplyTemplate();

    this.customRangeFinderContainer.DataContext = new ValueDataObject(this.rangeFinder1, "'$'0.00");

    ...

}

 

 

Customizing the RangeFinder

 

The third RangeFinder example from above re-templates the whole RangeFinder control and re-styles the GridSplitters. Even though the TextBlocks showing the Value and UpperValue properties are part of the GridSplitter style, the ValueDataObject instance in the parent's DataContext gives them something to bind to.

 

<Grid x:Name="flagRangeFinderContainer" Height="80" Width="300" Background="#FFDDDDDD">

    <local:RangeFinder x:Name="rangeFinder2" Height="70" Width="200" VerticalAlignment="Bottom" HorizontalAlignment="Center"

                       Minimum="10" Maximum="90" Value="20" UpperValue="70"

                       Background="Green">

        <local:RangeFinder.Style>

            <Style TargetType="local:RangeFinder">

                <Setter Property="Template">

                    <Setter.Value>

                        <ControlTemplate TargetType="local:RangeFinder">

                            <Grid x:Name="RootElement" Background="Transparent">

                                <Grid x:Name="TrackElement">

                                    <Grid.Resources>

                                        <SolidColorBrush x:Key="EndCap" Color="#55000000"/>

                                        <LinearGradientBrush x:Key="VerticalShadow" StartPoint="0,0" EndPoint="0,1">

                                            <GradientStopCollection>

                                                <GradientStop Color="Black" Offset="0"/>

                                                <GradientStop Color="Transparent" Offset="1"/>

                                            </GradientStopCollection>

                                        </LinearGradientBrush>

                                        <LinearGradientBrush x:Key="HorizontalShadow" StartPoint="0,0" EndPoint="1,0">

                                            <GradientStopCollection>

                                                <GradientStop Color="Black" Offset="0"/>

                                                <GradientStop Color="Transparent" Offset="1"/>

                                            </GradientStopCollection>

                                        </LinearGradientBrush>

                                        <LinearGradientBrush x:Key="SoutheastShadow" StartPoint="0,0" EndPoint="1,1">

                                            <GradientStopCollection>

                                                <GradientStop Color="Black" Offset=".32"/>

                                                <GradientStop Color="Transparent" Offset=".59"/>

                                            </GradientStopCollection>

                                        </LinearGradientBrush>

                                        <LinearGradientBrush x:Key="RaisedShadow" StartPoint="0,1" EndPoint="0,0">

                                            <GradientStopCollection>

                                                <GradientStop Color="#AA000000" Offset=".2"/>

                                                <GradientStop Color="Transparent" Offset=".8"/>

                                            </GradientStopCollection>

                                        </LinearGradientBrush>

                                        <LinearGradientBrush x:Key="TrackShadow" StartPoint="0,1" EndPoint="0,0">

                                            <GradientStopCollection>

                                                <GradientStop Color="#88000000" Offset=".2"/>

                                                <GradientStop Color="Transparent" Offset=".7"/>

                                                <GradientStop Color="Transparent" Offset=".8"/>

                                                <GradientStop Color="#88000000" Offset=".95"/>

                                            </GradientStopCollection>

                                        </LinearGradientBrush>

 

                                        <Style x:Key="valueTextBlockStyle" TargetType="TextBlock">

                                            <Setter Property="FontFamily" Value="Trebuchet MS"/>

                                            <Setter Property="FontSize" Value="11"/>

                                        </Style>

 

                                        <Style x:Key="RightGridSplitter" TargetType="GridSplitter">

                                            <Setter Property="Template">

                                                <Setter.Value>

                                                    <ControlTemplate TargetType="GridSplitter">

                                                        <Grid x:Name="RootElement" IsHitTestVisible="{TemplateBinding IsEnabled}">

                                                            <Grid.RowDefinitions>

                                                                <RowDefinition Height="26"/>

                                                                <RowDefinition Height="*"/>

                                                            </Grid.RowDefinitions>

                                                            <Grid.ColumnDefinitions>

                                                                <ColumnDefinition Width="6"/>

                                                                <ColumnDefinition Width="*"/>

                                                                <ColumnDefinition Width="6"/>

                                                            </Grid.ColumnDefinitions>

 

                                                            <!-- Inner Shadow -->

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-.1,5.9,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="4" Fill="{StaticResource VerticalShadow}"/>

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-.1,6,0,6" VerticalAlignment="Stretch" HorizontalAlignment="Left" Width="4" Fill="{StaticResource HorizontalShadow}">

                                                                <Rectangle.Clip>

                                                                    <PathGeometry>

                                                                        <PathGeometry.Figures>

                                                                            <PathFigure StartPoint="0,0">

                                                                                <PathFigure.Segments>

                                                                                    <LineSegment Point="5,5"/>

                                                                                    <LineSegment Point="5,26"/>

                                                                                    <LineSegment Point="0,26"/>

                                                                                </PathFigure.Segments>

                                                                            </PathFigure>

                                                                        </PathGeometry.Figures>

                                                                    </PathGeometry>

                                                                </Rectangle.Clip>

                                                            </Rectangle>

 

                                                            <!-- Outer Shadow -->

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="0,0,0,-3.9" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="4" Fill="{StaticResource VerticalShadow}"/>

                                                            <Rectangle Grid.Row="0" Grid.Column="2" Margin="0,2,-2.9,5" VerticalAlignment="Stretch" HorizontalAlignment="Right" Width="4" Fill="{StaticResource HorizontalShadow}"/>

                                                            <Rectangle Grid.Row="0" Grid.Column="2" Margin="0,0,-2,-3" HorizontalAlignment="Right" VerticalAlignment="Bottom" Height="8" Width="8" Fill="{StaticResource SoutheastShadow}"/>

 

                                                            <!-- Stem and Shadow -->

                                                            <Rectangle Grid.Row="1" Grid.Column="0" Margin="0,-1,0,-1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}"/>

                                                            <Rectangle Grid.Row="1" Grid.Column="0" Margin="0,-1,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource RaisedShadow}"/>

 

                                                            <!-- Flag Shape -->

                                                            <Polygon Grid.Row="0" Grid.Column="0" Points="0,26 0,5 5,0 6,0 6,26" Fill="{TemplateBinding Background}" />

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-1,0,0,0" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="6" />

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-1,0,0,0" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="6" />

                                                            <Polygon Grid.Row="0" Grid.Column="2" Margin="-1,0,0,0" Points="0,26 0,0 6,0 6,21 1,26" Fill="{TemplateBinding Background}"/>

 

                                                            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,2,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding FormattedUpperValue, Mode=OneWay}" Style="{StaticResource valueTextBlockStyle}" />

                                                        </Grid>

                                                    </ControlTemplate>

                                                </Setter.Value>

                                            </Setter>

                                        </Style>

                                        <Style x:Key="LeftGridSplitter" TargetType="GridSplitter">

                                            <Setter Property="Template">

                                                <Setter.Value>

                                                    <ControlTemplate TargetType="GridSplitter">

                                                        <Grid x:Name="RootElement" IsHitTestVisible="{TemplateBinding IsEnabled}">

                                                            <Grid.RowDefinitions>

                                                                <RowDefinition Height="26"/>

                                                                <RowDefinition Height="*"/>

                                                            </Grid.RowDefinitions>

                                                            <Grid.ColumnDefinitions>

                                                                <ColumnDefinition Width="6"/>

                                                                <ColumnDefinition Width="*"/>

                                                                <ColumnDefinition Width="6"/>

                                                            </Grid.ColumnDefinitions>

 

                                                            <!-- Inner Shadow -->

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-.1,5.9,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="4" Fill="{StaticResource VerticalShadow}"/>

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-.1,6,0,6" VerticalAlignment="Stretch" HorizontalAlignment="Left" Width="4" Fill="{StaticResource HorizontalShadow}">

                                                                <Rectangle.Clip>

                                                                    <PathGeometry>

                                                                        <PathGeometry.Figures>

                                                                            <PathFigure StartPoint="0,0">

                                                                                <PathFigure.Segments>

                                                                                    <LineSegment Point="5,5"/>

                                                                                    <LineSegment Point="5,26"/>

                                                                                    <LineSegment Point="0,26"/>

                                                                                </PathFigure.Segments>

                                                                            </PathFigure>

                                                                        </PathGeometry.Figures>

                                                                    </PathGeometry>

                                                                </Rectangle.Clip>

                                                            </Rectangle>

 

                                                            <!-- Outer Shadow -->

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="0,0,0,-3.9" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="4" Height="4" Fill="{StaticResource VerticalShadow}">

                                                                <Rectangle.Clip>

                                                                    <PathGeometry>

                                                                        <PathGeometry.Figures>

                                                                            <PathFigure StartPoint="0,0">

                                                                                <PathFigure.Segments>

                                                                                    <LineSegment Point="5,5"/>

                                                                                    <LineSegment Point="26,5"/>

                                                                                    <LineSegment Point="26,0"/>

                                                                                </PathFigure.Segments>

                                                                            </PathFigure>

                                                                        </PathGeometry.Figures>

                                                                    </PathGeometry>

                                                                </Rectangle.Clip>

                                                            </Rectangle>

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="3.9,0,0,-3.9" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="4" Fill="{StaticResource VerticalShadow}"/>

                                                            <Rectangle Grid.Row="0" Grid.Column="2" Grid.RowSpan="2"  Margin="0,7,-3.9,0" VerticalAlignment="Stretch" HorizontalAlignment="Right" Width="4" Fill="{StaticResource HorizontalShadow}">

                                                                <Rectangle.Clip>

                                                                    <PathGeometry>

                                                                        <PathGeometry.Figures>

                                                                            <PathFigure StartPoint="0,0">

                                                                                <PathFigure.Segments>

                                                                                    <LineSegment Point="5,5"/>

                                                                                    <LineSegment Point="5,26"/>

                                                                                    <LineSegment Point="0,26"/>

                                                                                </PathFigure.Segments>

                                                                            </PathFigure>

                                                                        </PathGeometry.Figures>

                                                                    </PathGeometry>

                                                                </Rectangle.Clip>

                                                            </Rectangle>

 

                                                            <!-- Stem and Shadow -->

                                                            <Rectangle Grid.Row="1" Grid.Column="2" Margin="0,-1,0,-1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}"/>

                                                            <Rectangle Grid.Row="1" Grid.Column="2" Margin="0,-1,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource RaisedShadow}"/>

 

                                                            <!-- Flag Shape -->

                                                            <Polygon Grid.Row="0" Grid.Column="2" Points="0,26 0,0 1,0 6,5 6,26" Fill="{TemplateBinding Background}" />

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-1,0,-1,0" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="6" />

                                                            <Rectangle Grid.Row="0" Grid.Column="1" Margin="-1,0,-1,0" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Height="6" />

                                                            <Polygon Grid.Row="0" Grid.Column="0" Margin="0,0,-1,0" Points="0,21 0,0 6,0 6,26 5,26" Fill="{TemplateBinding Background}"/>

 

                                                            <TextBlock Grid.Row="0" Grid.Column="1" Margin="0,2,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding FormattedValue, Mode=OneWay}" Style="{StaticResource valueTextBlockStyle}"/>

                                                        </Grid>

                                                    </ControlTemplate>

                                                </Setter.Value>

                                            </Setter>

                                        </Style>

                                    </Grid.Resources>

                                    <Grid.ColumnDefinitions>

                                        <ColumnDefinition Width="*"/>

                                        <ColumnDefinition Width="*"/>

                                        <ColumnDefinition Width="*"/>

                                    </Grid.ColumnDefinitions>

                                    <Grid.RowDefinitions>

                                        <RowDefinition Height="*"/>

                                        <RowDefinition Height="6"/>

                                        <RowDefinition Height="*"/>

                                    </Grid.RowDefinitions>

 

                                    <!-- Placeholder rectangles for measuring - should never be displayed -->

                                    <Rectangle x:Name="LeftTrackElement" Grid.Column="0" Grid.RowSpan="3" Fill="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

                                    <Rectangle x:Name="RightTrackElement" Grid.Column="2" Grid.RowSpan="3" Fill="Transparent" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

 

                                    <!-- The two thumbs -->

                                    <GridSplitter x:Name="LeftGridSplitterElement" Style="{StaticResource LeftGridSplitter}" Grid.Column="0" HorizontalAlignment="Right" Width="{TemplateBinding ThumbWidth}" Background="{TemplateBinding Background}" VerticalAlignment="Bottom" Height="30"/>

                                    <GridSplitter x:Name="RightGridSplitterElement" Style="{StaticResource RightGridSplitter}" Grid.Column="2" HorizontalAlignment="Left" Width="{TemplateBinding ThumbWidth}" Background="{TemplateBinding Background}" VerticalAlignment="Bottom" Height="30"/>

 

                                    <!-- The track and its shadow -->

                                    <Rectangle Grid.Row="1" Margin="{TemplateBinding TrackMargin}"  Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{TemplateBinding Background}"/>

                                    <Rectangle Grid.Row="1" Margin="{TemplateBinding TrackMargin}"  Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource TrackShadow}"/>

 

                                    <!-- The end caps for the track -->

                                    <Rectangle Grid.Row="1" Margin="{TemplateBinding TrackMargin}" Width="1" HorizontalAlignment="Left" VerticalAlignment="Stretch" Fill="{StaticResource EndCap}"/>

                                    <Rectangle Grid.Row="1" Grid.Column="2" Margin="{TemplateBinding TrackMargin}" Width="1" HorizontalAlignment="Right" VerticalAlignment="Stretch" Fill="{StaticResource EndCap}"/>

                                </Grid>

                            </Grid>

                        </ControlTemplate>

                    </Setter.Value>

                </Setter>

                <Setter Property="Background" Value="DarkGray"/>

                <Setter Property="ThumbWidth" Value="40"/>

            </Style>

        </local:RangeFinder.Style>

    </local:RangeFinder>

</Grid>

 

 

There are some things the RangeFinder sample code is missing - fancy state transitions, rock-solid coercion, and thorough testing. The code is provided as-is but feel free to party away and mutate it in to your own concoction.

Re-templating a WatermarkedTextBox
04 April 08 07:32 PM | keith.jones | 0 Comments   

Hosted by Silverlight Streaming  

Ta-da! Here's a new look for the WatermarkedTextBox (a.k.a. WTB) in Silverlight 2 Beta 1. This was relatively simple to do just by changing the template.

Here is the XAML for the template:

    <ControlTemplate x:Key="wtbTemplate"  TargetType="WatermarkedTextBox">

        <Grid x:Name="RootElement" >

            <Grid.Resources>

                <Storyboard x:Key="Disabled State">

                    <DoubleAnimation Storyboard.TargetName="DisabledVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0.5" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard x:Key="Disabled Watermarked State">

                    <DoubleAnimation Storyboard.TargetName="DisabledVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0.5" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard x:Key="Normal State">

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard x:Key="Focused State">

                    <DoubleAnimation Storyboard.TargetName="FocusVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.1"/>

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard x:Key="MouseOver State">

                    <!--<DoubleAnimation Storyboard.TargetName="FocusVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.1"/>-->

                    <DoubleAnimation Storyboard.TargetName="MouseOverVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.1"/>

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard  x:Key="Normal Watermarked State">

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                </Storyboard>

                <Storyboard x:Key="MouseOver Watermarked State">

                    <!--<DoubleAnimation Storyboard.TargetName="FocusVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.1"/>-->

                    <DoubleAnimation Storyboard.TargetName="MouseOverVisual"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.1"/>

                    <DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

                                     Storyboard.TargetProperty="Opacity"

                                     To="0" Duration="0:0:0.0"/>

                    <DoubleAnimation Storyboard.TargetName="WatermarkElement"

                                     Storyboard.TargetProperty="Opacity"

                                     To="1" Duration="0:0:0.0"/>

                </Storyboard>

            </Grid.Resources>

           

            <!-- To get a white background -->

            <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

                       RadiusX="5" RadiusY="5" Opacity="1" Fill="White"/>

           

            <!-- Simple drop shadow effect on Mouse Over -->

            <Grid x:Name="MouseOverVisual" Opacity="0">

                <Rectangle Height="10" Width="10" RadiusX="5" RadiusY="5"

                           HorizontalAlignment="Right" VerticalAlignment="Bottom"

                           Stroke="Gray" StrokeThickness="4" Margin="0,0,-2,-2">

                    <Rectangle.Clip>

                        <RectangleGeometry Rect="4,4,6,6"/>

                    </Rectangle.Clip>

                </Rectangle>

                <Rectangle Width="3" RadiusX="2" RadiusY="2" HorizontalAlignment="Right"

                           VerticalAlignment="Stretch"  Fill="Gray" Margin="0,5,-2,3"/>

                <Rectangle Height="3" RadiusX="2" RadiusY="2" HorizontalAlignment="Stretch"

                           VerticalAlignment="Bottom"  Fill="Gray" Margin="5,0,3,-2"/>

            </Grid>

           

            <Grid>

                <Grid.RowDefinitions>

                    <RowDefinition Height="3*"/>

                    <RowDefinition Height="5*"/>

                </Grid.RowDefinitions>

               

                <!-- Top of tag-->

                <Rectangle Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

                           RadiusX="5" RadiusY="5" Fill="LimeGreen"/>

                <Rectangle Grid.Row="0" Height="5" HorizontalAlignment="Stretch"

                           VerticalAlignment="Bottom" Fill="LimeGreen"/>

                <TextBlock Grid.Row="0" Text="Hello, my name is ..." FontFamily="Comic Sans MS"

                           FontSize="24" Foreground="White" VerticalAlignment="Center" Margin="5,0,0,0"/>

 

                <!-- Bottom of tag where the name is typed -->

                <Border x:Name="ELEMENT_Content" Grid.Row="1" Background="Transparent"

                        BorderBrush="Transparent" BorderThickness="0" HorizontalAlignment="Stretch"

                        VerticalAlignment="Stretch" Padding="10,0,0,0"/>

                <ContentControl

                    Grid.Row="1"

                      x:Name="WatermarkElement"

                      IsTabStop="False"

                      IsHitTestVisible="False"

                      Content="{TemplateBinding Watermark}"

                      Foreground="Gray"

                      Background="{TemplateBinding Background}"

                      FontFamily="{TemplateBinding FontFamily}"

                      FontSize="{TemplateBinding FontSize}"

                      FontStretch="{TemplateBinding FontStretch}"

                      FontStyle="{TemplateBinding FontStyle}"

                      FontWeight="{TemplateBinding FontWeight}"

                      Padding="10,0,0,0"/>

            </Grid>

           

            <!-- A nice black border on top of it all-->

            <Rectangle StrokeThickness="2" Stroke="Black" Fill="Transparent" RadiusX="5" RadiusY="5"

                       HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

           

            <!-- Show a star if the control has focus -->

            <Polygon x:Name="FocusVisual"  Stroke="Blue" StrokeThickness="1.0" Fill="Blue" Opacity="0"

                     Stretch="Fill" Height="50" Width="50"

                     HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-25,-20,0"

                     Points="317.667,129.333 308.043,112.31 320.505,97.2405 301.341,101.133 290.86,84.6235 288.64,104.052 269.7,108.919 287.492,117.034 286.267,136.551 299.483,122.138 317.667,129.333"/>

           

            <!-- Put a semi-transparent mask over the control if it is disabled -->

            <Rectangle x:Name="DisabledVisual" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

                       RadiusX="5" RadiusY="5" Opacity="0" Fill="#55AAAAAA"/>

        </Grid>

    </ControlTemplate>

Here is the XAML for adding the templated WTB to a page:

    <WatermarkedTextBox Template="{StaticResource wtbTemplate}" Height="100" Width="300" FontFamily="Comic Sans MS" FontSize="36" Watermark="&lt;Bob&gt;"/>

Here are some details for what it took.

1.     Even though I wasn’t changing the watermark or disabled behavior I still had to redefine all of the related template parts.

a.     The WatermarkElement ContentControl is used to host the actual watermark text (or other content if applicable).

b.    The ELEMENT_Content Border is required because that is how the internal text box code knows where to draw the caret and the text that the user types.

c.     The DisabledVisual UI element is not part of the template part contract, but the default disabled state needs it to prevent user interaction with the control.

d.    The Disabled State, Disabled Watermarked State, Normal State, and Normal Watermarked State storyboards all needed to be redefined even if the animations they contained didn’t change. This is a side effect of the control template parts model.

2.     I wanted to change the layout and the focus and mouse over visuals.

a.     I used a nested grid to help layout all of the different pieces of the UI.

b.    I created a drop shadow UI element that I called MouseOverVisual and a star UI element that I called FocusedVisual.

c.     Notice that the names don’t end with “Element”. The convention for the control template parts model is that only those UI elements that are actually part of the contract and used by the internal control code should end with “Element”.

d.    Just adding the visuals wasn’t enough, I needed to update the Focused State, MouseOver State, and MouseOver Watermarked State storyboards to change the opacity of the visuals for the appropriate user actions.

e.     Bug Alert! The internal code for the WTB has a flaw that causes the MouseOver states to always override the Focused state. Therefore, if you use the mouse to set the focus on the control, the FocusVisual UI element never gets displayed. However, if you tab to the control to set the focus, the visual will get displayed (try it out – tab between the default WTB and the re-templated WTB). You could always uncomment the commented out code in the MouseOver storyboards to combine the two behaviors. This flaw isn’t obvious in the default WTB because there aren’t separate UI elements for the focus and mouse over states.

3.     Lastly, I needed to set the Template property and not the Style property of the target WTB due to the fact that the Template is set in the constructor in WTB. This is discussed a little more in one of my other posts.

 

How to set the XAML editor to full view by default
10 March 08 05:50 AM | keith.jones | 0 Comments   

For hard-core XAML programmers or just to boost performance when working with XAML files in VS 2008, you may want to have the default view be just the XAML editor (no designer visible). To do so, select the Tools->Options... menu item. Then navigate in the tree view to Text Editor->XAML->Miscellaneous. Check the box for "Always open documents in full XAML view". Click OK to save.

Filed under: , , ,
Silverlight 2 Beta 1 Hints
05 March 08 11:53 PM | keith.jones | 2 Comments   

Remember, it is a beta so there are going to be some things that are by design and others which just need temporary workarounds. Here are some things I learned when developing and testing some of the controls delivered as part of the Silverlight 2 Beta 1 SDK.

1.      Unlike WPF, the xaml in Silverlight 2 is parsed at runtime, not compile time. (By Design)

 

2.      The Style property can only be set once on a control. If you are creating a custom control don't set the Style property in the constructor or nobody will be able to style your control.

 

3.      Setters in styles do not override any properties set in code. For example, if the constructor of a custom control calls this.Template = someTemplate, then there is no way for a Setter in a style to  change the Template property. However, setting the property directly in xaml does override the value set in constructor. (By Design)

 

4.      Bindings using dependency properties as sources do not notify the target when the source changes. To workaround this issue temporarily, you should create a business object that implements the INotifyPropertyChanged interface and can be used as a proxy for any dynamic data binding that you may need to do.

 

5.      If you set the Template attribute of a control (whether through a Setter property in a Style or by directly setting the Template property) you will need to redefine the animation storyboards to get the state transition effects (e.g. MouseOver). For examples of the default templates from which you can copy and paste, check out http://msdn2.microsoft.com/en-us/library/cc278075(vs.95).aspx . (By Design)

 

6.      Anything that uses a ContentControl to display text (like Button) cannot simply use styles to change the FontFamily, FontStretch, FontSource, FontStyle, and FontWeight properties. This is a bug that will be fixed in future releases. Instead, you will have to explicitly set those properties on each instance of the element. For example

<Style x:Name="topLink" TargetType="HyperlinkButton">

    <Setter Property="FontSize" Value="24" />

    <Setter Property="Foreground" Value="#000000" />

</Style>

<HyperlinkButton Style="{StaticResource topLink}" FontFamily="Verdana"/>

 

7.      You cannot set the Style property for the TextBox or (correction) WatermarkedTextBox controls because the controls currently set the Style in the constructor (see #3). To change the appearance of these controls you need to set the Template property explicitly

<WatermarkedTextBox  Template="{StaticResource wtbTemplate}"/>

8.      You can only call the Begin() method on a storyboard after the object has been added to the visual tree. Specifically, if you construct a brand new control but don’t add it to a visible parent, calling Begin() on a storyboard affecting that control will throw an exception.

Page view tracker