Tuesday, January 13, 2009

How to get an element from a grid using x,y coordinates

Occasionaly you might come up against the requirement to get a list of UIElements that occupy a specific row and column within a Grid control. This might occur when the rows and columns are not only used for layout, but also map to some real world data. The example that comes to mind is a calendar control where the columns represent dates and the rows are time spans within the day.

Although it's easy to assign UIElements to rows and columns using the Grid.SetRow/Column attached property setters, there is no API that allows you to ask the grid what elements exist at a given location.

This extension method uses a simple linq expression to find any child elements of the grid that occupy the specified row and column.
public static Collection<TElement> GetElements<TElement>(this Grid grid, int row, int column) 
where TElement : UIElement
{
var elements = from UIElement element in grid.Children
where element is TElement &&
Grid.GetRow(element) == row &&
Grid.GetColumn(element) == column
select element as TElement;
return new Collection<TElement>(elements.ToList());
}

Sunday, January 11, 2009

Loading animation


This animation was inspired by well known video sharing site, and it's handy when you need to block the UI during a long running process. It will dim any controls placed underneath it and block mouse access to those controls.






The concept is fairly simple, just a style that targets Control. It contains a series of ellipses layed out in a circle on a canvas. When the control is set to visible the animation starts, each ellipse is targeted by a storyboard which lasts just over one and a half seconds. These storyboard simply animate the fill from opage to transparent.

You can download the full source from here.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Color x:Key="FilledColor" A="255" B="155" R="155" G="155"/>
<Color x:Key="UnfilledColor" A="0" B="155" R="155" G="155"/>

<Style x:Key="BusyAnimationStyle" TargetType="Control">
<Setter Property="Background" Value="#7F000000"/>

<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ControlTemplate.Resources>
<Storyboard x:Key="Animation0" BeginTime="00:00:00.0" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse0"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation1" BeginTime="00:00:00.2" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse1"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation2" BeginTime="00:00:00.4" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse2"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation3" BeginTime="00:00:00.6" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse3"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation4" BeginTime="00:00:00.8" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse4"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation5" BeginTime="00:00:01.0" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse5"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation6" BeginTime="00:00:01.2" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse6"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>

<Storyboard x:Key="Animation7" BeginTime="00:00:01.4" RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="ellipse7"
Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
>
<SplineColorKeyFrame KeyTime="00:00:00.0" Value="{StaticResource FilledColor}"/>
<SplineColorKeyFrame KeyTime="00:00:01.6" Value="{StaticResource UnfilledColor}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>

<ControlTemplate.Triggers>
<Trigger Property="IsVisible" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource Animation0}" x:Name="Storyboard0" />
<BeginStoryboard Storyboard="{StaticResource Animation1}" x:Name="Storyboard1"/>
<BeginStoryboard Storyboard="{StaticResource Animation2}" x:Name="Storyboard2"/>
<BeginStoryboard Storyboard="{StaticResource Animation3}" x:Name="Storyboard3"/>
<BeginStoryboard Storyboard="{StaticResource Animation4}" x:Name="Storyboard4"/>
<BeginStoryboard Storyboard="{StaticResource Animation5}" x:Name="Storyboard5"/>
<BeginStoryboard Storyboard="{StaticResource Animation6}" x:Name="Storyboard6"/>
<BeginStoryboard Storyboard="{StaticResource Animation7}" x:Name="Storyboard7"/>
</Trigger.EnterActions>

<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="Storyboard0"/>
<StopStoryboard BeginStoryboardName="Storyboard1"/>
<StopStoryboard BeginStoryboardName="Storyboard2"/>
<StopStoryboard BeginStoryboardName="Storyboard3"/>
<StopStoryboard BeginStoryboardName="Storyboard4"/>
<StopStoryboard BeginStoryboardName="Storyboard5"/>
<StopStoryboard BeginStoryboardName="Storyboard6"/>
<StopStoryboard BeginStoryboardName="Storyboard7"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>

<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
>
<Canvas Height="60" Width="60">
<Canvas.Resources>
<Style TargetType="Ellipse">
<Setter Property="Width" Value="15"/>
<Setter Property="Height" Value="15" />
<Setter Property="Fill" Value="#009B9B9B" />
</Style>
</Canvas.Resources>

<Ellipse x:Name="ellipse0" Canvas.Left="1.75" Canvas.Top="21"/>
<Ellipse x:Name="ellipse1" Canvas.Top="7" Canvas.Left="6.5"/>
<Ellipse x:Name="ellipse2" Canvas.Left="20.5" Canvas.Top="0.75"/>
<Ellipse x:Name="ellipse3" Canvas.Left="34.75" Canvas.Top="6.75"/>
<Ellipse x:Name="ellipse4" Canvas.Left="40.5" Canvas.Top="20.75" />
<Ellipse x:Name="ellipse5" Canvas.Left="34.75" Canvas.Top="34.5"/>
<Ellipse x:Name="ellipse6" Canvas.Left="20.75" Canvas.Top="39.75"/>
<Ellipse x:Name="ellipse7" Canvas.Top="34.25" Canvas.Left="7" />
<Ellipse Width="39.5" Height="39.5" Canvas.Left="8.75" Canvas.Top="8" Visibility="Hidden"/>
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

Thursday, January 8, 2009

Displaying grid lines

Sometimes when laying out a Grid control in WPF you need to add grid lines to identify rows and columns. The Grid control does expose a propery called ShowGridLines, but you have no control over the appearence of the lines. According to the documentation this property is only provided for debugging purposes and should not be used in production applications.

Having face this problem myself I came up with a set of styles that could be used to produce grid lines.

There are three main aspects that makes these styles work.
  1. Line.Stretch is set to fill. 
  2. For horizontal lines the VerticalAlignment of the line is set Bottom, and for VerticalLines the HorizontalAlignment is set to Right.
  3. The line how many rows or columns to span, this is done by binding to either RowDefinitions or ColumnDefintions Count property.
You can download the full source from here.

EDIT:
Sample updated to display the grid lines at the top and side of the grid.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    >
    <Style x:Key="lineStyle" TargetType="Line">
        <Setter Property="Stroke" Value="Gray" />
        <Setter Property="Stretch" Value="Fill" />
        <Setter Property="Grid.ZIndex" Value="100" />
        <Setter Property="StrokeDashArray" Value="1,2" />
    </Style>

    <Style x:Key="horizontalLineStyle" TargetType="Line" BasedOn="{StaticResource lineStyle}">
        <Setter Property="X2" Value="1" />
        <Setter Property="VerticalAlignment" Value="Bottom" />
        <Setter Property="Grid.ColumnSpan" Value="{Binding 
            Path=ColumnDefinitions.Count, RelativeSource={RelativeSource AncestorType=Grid}}"/>
    </Style>

    <Style x:Key="verticalLineStyle" TargetType="Line" BasedOn="{StaticResource lineStyle}">
        <Setter Property="Y2" Value="1" />
        <Setter Property="HorizontalAlignment" Value="Right" />
        <Setter Property="Grid.RowSpan" Value="{Binding 
            Path=RowDefinitions.Count, RelativeSource={RelativeSource AncestorType=Grid}}"/>
    </Style>

    <Style x:Key="verticalLineStyleLeft" TargetType="Line" BasedOn="{StaticResource verticalLineStyle}">
        <Setter Property="HorizontalAlignment" Value="Left" />
    </Style>   
    
    <Style x:Key="horizontalLineStyleTop" TargetType="Line" BasedOn="{StaticResource horizontalLineStyle}">
        <Setter Property="VerticalAlignment" Value="Top" />
    </Style>    
</ResourceDictionary>

The Spinning Donut

When running long operations across the web or other connected devices it's a good idea to provide some visual feedback to the user to indicate activity.

The spinning donut provides a simple xaml only solution. It's simply a ControlTemplate that targets Control. 

You can download the full source code from here.


<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<LinearGradientBrush x:Key="donutBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF626262" Offset="0"/>
<GradientStop Color="#FFCCC9C9" Offset="1"/>
</LinearGradientBrush>

<Style TargetType="Control">
<Setter Property="Background" Value="{StaticResource donutBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ControlTemplate.Resources>
<Storyboard x:Key="startSpinning" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="donut"
Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)"
>
<SplineDoubleKeyFrame KeyTime="00:00:00.2500000" Value="180"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>

<!-- whenvever the control becomes visible begin the startSpinning storyboard -->
<ControlTemplate.Triggers>
<Trigger Property="IsVisible" Value="True">
<Trigger.EnterActions>
<BeginStoryboard
x:Name="startSpinningStoryboard"
Storyboard="{StaticResource startSpinning}"
/>
</Trigger.EnterActions>

<Trigger.ExitActions>
<StopStoryboard BeginStoryboardName="startSpinningStoryboard"/>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>

<!-- the visual is a path that describes two circles, one inside the
other to form a donut. This allows for any background color
to pass through the control
-->
<Viewbox Stretch="Fill">
<Path
x:Name="donut"
Fill="{TemplateBinding Background}"
Stretch="Fill"
RenderTransformOrigin="0.5,0.5"
Data=
"M12.526,6.132 C8.9946932,6.132 6.132,8.9946932 6.132,12.526 6.132,16.057307 8.9946897,
18.92 12.526,18.92 16.057311,18.92 18.92,16.057311 18.92,12.526 18.92,8.9946897 16.057307,
6.132 12.526,6.132 z M12.5,0 C19.403552,0 25,5.596434 25,12.5 25,19.40356 19.40356,25 12.5,
25 5.596434,25 0,19.403552 0,12.5 0,5.5964418 5.5964418,0 12.5,0 z"
>
<!-- The storybaord animates the angle of the rotation which causes
the donut to spin.
-->
<Path.RenderTransform>
<RotateTransform Angle="0"/>
</Path.RenderTransform>
</Path>
</Viewbox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>