Blog

posts tagged with csharp

XAML Animated Headered TextBox Style (Part 2)

0 Comments
By Fons Sonnemans, 6-3-2017

Last week I wrote a blog item about the XAML Animated Headered TextBox Style. I got a lot of reactions on Twitter. Pieter Otten challenged me into adjusting my solution to make it conform the Material Design guidelines. In Android the header (Label) of the TextBox (Text Field) animates on Focus not when you enter a value. I tried to implement this by adjusting my style but that didn't work. I had to introduce an extra behavior to get it to work. See the result in this video.

AnimatedHeaderedTextBoxStyle demo video which works on Focus

MoveHeaderOnFocusBehavior

The MoveHeaderOnFocusBehavior is used to set the Visual States (Empty and NotEmpty) of the associated TextBox when it is loaded, gets the focus, loses the focus or when the text changes. This Behavior was build by subclassing the Behavior<T> class from the Microsoft.Xaml.Behaviors.Uwp.Managed NuGet package.

public class MoveHeaderOnFocusBehavior : Behavior<TextBox> {

    private bool _hasFocus;

    private void AssociatedObject_GotFocus(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
        _hasFocus = true;
        UpdateState(true);
    }

    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e) {
        UpdateState(false);
        this.AssociatedObject.Loaded -= this.AssociatedObject_Loaded;
    }

    private void AssociatedObject_LostFocus(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
        _hasFocus = false;
        UpdateState(true);
    }

    private void AssociatedObject_TextChanged(object sender, TextChangedEventArgs e) {
        UpdateState(false);
    }

    protected override void OnAttached() {
        base.OnAttached();
        this.AssociatedObject.Loaded += this.AssociatedObject_Loaded;
        this.AssociatedObject.GotFocus += this.AssociatedObject_GotFocus;
        this.AssociatedObject.LostFocus += this.AssociatedObject_LostFocus;
        this.AssociatedObject.TextChanged += this.AssociatedObject_TextChanged;
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        this.AssociatedObject.GotFocus -= this.AssociatedObject_GotFocus;
        this.AssociatedObject.LostFocus -= this.AssociatedObject_LostFocus;
        this.AssociatedObject.TextChanged -= this.AssociatedObject_TextChanged;
    }


    private void UpdateState(bool animate) {
        if (_hasFocus || !string.IsNullOrEmpty(this.AssociatedObject.Text)) {
            VisualStateManager.GoToState(this.AssociatedObject, "NotEmpty", animate);
        } else {
            VisualStateManager.GoToState(this.AssociatedObject, "Empty", animate);
        }
    }

}

 

Usage

I dragged and dropped the MoveHeaderOnFocusBehavior from the Assets window in Blend for Visual Studio to every TextBox of the Style 'AnimatedHeaderedTextBoxStyle'. That's all.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App121"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      x:Class="App121.MainPage"
      mc:Ignorable="d">

    <StackPanel Width="300"
                Margin="0,0,60,0"
                VerticalAlignment="Top"
                HorizontalAlignment="Center">

        <TextBox Header="Firstname"
                 Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                 IsSpellCheckEnabled="False"
                 InputScope="PersonalFullName">
            <Interactivity:Interaction.Behaviors>
                <local:MoveHeaderOnFocusBehavior />
            </Interactivity:Interaction.Behaviors>
        </TextBox>

        <TextBox Header="Lastname"
                 Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                 IsSpellCheckEnabled="False"
                 Margin="0,8"
                 InputScope="PersonalFullName">
            <Interactivity:Interaction.Behaviors>
                <local:MoveHeaderOnFocusBehavior />
            </Interactivity:Interaction.Behaviors>
        </TextBox>

        <TextBox Header="E-mail"
                 x:Name="textBoxEmail"
                 Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                 IsSpellCheckEnabled="False"
                 Text="fons.sonnemans@reflectionit.nl"
                 InputScope="EmailNameOrAddress">
            <Interactivity:Interaction.Behaviors>
                <local:MoveHeaderOnFocusBehavior />
            </Interactivity:Interaction.Behaviors>
        </TextBox>

        <Button Content="Next"
                Click="Button_Click"
                Margin="0,16,0,0" />

        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
              Height="640"
              Margin="0,0,-60,0" />
    </StackPanel>
</Page>

 

The code

I have published the updated code on GitHub. I hope you like it.

I also received some Twitter reactions pointing to this great Inspirational TextBox styles for Windows Phone & Store blogpost from Deani Hansen. That solution uses Styling and subclassing the TextBox control. As always there are many solutions for the same problem.

Fons

XAML Animated Headered TextBox Style

0 Comments
By Fons Sonnemans, 28-2-2017

A while ago I saw a beautiful effect in an Android app which I was using. It had textboxes with a placeholder text. This placeholder text moved to the header when you enter the first character in the textbox. This triggered me to do the same in a Windows 10 XAML app (UWP). This is a video of the result.

AnimatedHeaderedTextBoxStyle demo video

AnimatedHeaderedTextBoxStyle

My solution is quite simple. I created a Style for a TextBox which has an extra StateGroup named 'HeaderStates'. There are two States in this group named Empty and NotEmpty. The HeaderContentPresenter is moved using TranslateX and TranslateY setters to the correct position. The states are triggered using TemplatedParent databinding to the Text property. I have used a converter which returns true or false when the text is empty or not. I have also changed the BorderThickness property of the Style. It now has only a bottom line and is not boxed any more.

<Style x:Key="AnimatedHeaderedTextBoxStyle"
        TargetType="TextBox">
    <Setter Property="MinWidth"
            Value="{ThemeResource TextControlThemeMinWidth}" />
    <Setter Property="MinHeight"
            Value="{ThemeResource TextControlThemeMinHeight}" />
    <Setter Property="Foreground"
            Value="{ThemeResource TextControlForeground}" />
    <Setter Property="Background"
            Value="{ThemeResource TextControlBackground}" />
    <Setter Property="BorderBrush"
            Value="{ThemeResource TextControlBorderBrush}" />
    <Setter Property="SelectionHighlightColor"
            Value="{ThemeResource TextControlSelectionHighlightColor}" />
    <Setter Property="BorderThickness"
            Value="0,0,0,2" />
    <Setter Property="FontFamily"
            Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontSize"
            Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="ScrollViewer.HorizontalScrollMode"
            Value="Auto" />
    <Setter Property="ScrollViewer.VerticalScrollMode"
            Value="Auto" />
    <Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
            Value="Hidden" />
    <Setter Property="ScrollViewer.VerticalScrollBarVisibility"
            Value="Hidden" />
    <Setter Property="ScrollViewer.IsDeferredScrollingEnabled"
            Value="False" />
    <Setter Property="Padding"
            Value="{ThemeResource TextControlThemePadding}" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="TextBox">
                <Grid>
                    <Grid.Resources>
                        <Style x:Name="DeleteButtonStyle"
                                TargetType="Button">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="Button">
                                        <Grid x:Name="ButtonLayoutGrid"
                                                BorderBrush="{ThemeResource TextControlButtonBorderBrush}"
                                                BorderThickness="{TemplateBinding BorderThickness}"
                                                Background="{ThemeResource TextControlButtonBackground}">
                                            <VisualStateManager.VisualStateGroups>
                                                <VisualStateGroup x:Name="CommonStates">
                                                    <VisualState x:Name="Normal" />
                                                    <VisualState x:Name="PointerOver">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                                            Storyboard.TargetName="ButtonLayoutGrid">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonBackgroundPointerOver}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
                                                                                            Storyboard.TargetName="ButtonLayoutGrid">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonBorderBrushPointerOver}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                                            Storyboard.TargetName="GlyphElement">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonForegroundPointerOver}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Pressed">
                                                        <Storyboard>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                                            Storyboard.TargetName="ButtonLayoutGrid">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonBackgroundPressed}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
                                                                                            Storyboard.TargetName="ButtonLayoutGrid">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonBorderBrushPressed}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                                            Storyboard.TargetName="GlyphElement">
                                                                <DiscreteObjectKeyFrame KeyTime="0"
                                                                                        Value="{ThemeResource TextControlButtonForegroundPressed}" />
                                                            </ObjectAnimationUsingKeyFrames>
                                                        </Storyboard>
                                                    </VisualState>
                                                    <VisualState x:Name="Disabled">
                                                        <Storyboard>
                                                            <DoubleAnimation Duration="0"
                                                                                To="0"
                                                                                Storyboard.TargetProperty="Opacity"
                                                                                Storyboard.TargetName="ButtonLayoutGrid" />
                                                        </Storyboard>
                                                    </VisualState>
                                                </VisualStateGroup>
                                            </VisualStateManager.VisualStateGroups>
                                            <TextBlock x:Name="GlyphElement"
                                                        AutomationProperties.AccessibilityView="Raw"
                                                        Foreground="{ThemeResource TextControlButtonForeground}"
                                                        FontStyle="Normal"
                                                        FontSize="12"
                                                        FontFamily="{ThemeResource SymbolThemeFontFamily}"
                                                        HorizontalAlignment="Center"
                                                        Text="&#xE10A;"
                                                        VerticalAlignment="Center" />
                                        </Grid>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </Grid.Resources>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="HeaderContentPresenter">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlHeaderForegroundDisabled}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBackgroundDisabled}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBorderBrushDisabled}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="ContentElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlForegroundDisabled}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="PlaceholderTextContentPresenter">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlPlaceholderForegroundDisabled}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBorderBrushPointerOver}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBackgroundPointerOver}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="PlaceholderTextContentPresenter">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlPlaceholderForegroundPointerOver}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="ContentElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlForegroundPointerOver}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="PlaceholderTextContentPresenter">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlPlaceholderForegroundFocused}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBackgroundFocused}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush"
                                                                    Storyboard.TargetName="BorderElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlBorderBrushFocused}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground"
                                                                    Storyboard.TargetName="ContentElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="{ThemeResource TextControlForegroundFocused}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="RequestedTheme"
                                                                    Storyboard.TargetName="ContentElement">
                                        <DiscreteObjectKeyFrame KeyTime="0"
                                                                Value="Light" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="ButtonStates">
                            <VisualState x:Name="ButtonVisible">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
                                                                    Storyboard.TargetName="DeleteButton">
                                        <DiscreteObjectKeyFrame KeyTime="0">
                                            <DiscreteObjectKeyFrame.Value>
                                                <Visibility>Visible</Visibility>
                                            </DiscreteObjectKeyFrame.Value>
                                        </DiscreteObjectKeyFrame>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="ButtonCollapsed" />
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="HeaderStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition GeneratedDuration="0:0:0.35">
                                    <VisualTransition.GeneratedEasingFunction>
                                        <CircleEase EasingMode="EaseInOut" />
                                    </VisualTransition.GeneratedEasingFunction>
                                </VisualTransition>
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="NotEmpty">
                                <VisualState.Setters>
                                    <Setter Target="HeaderContentPresenter.(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
                                            Value="0" />
                                    <Setter Target="HeaderContentPresenter.(UIElement.RenderTransform).(CompositeTransform.TranslateX)"
                                            Value="0" />
                                </VisualState.Setters>
                                <VisualState.StateTriggers>
                                    <StateTrigger IsActive="{Binding Text, Converter={StaticResource IsNotEmptyConverter}, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                                </VisualState.StateTriggers>
                            </VisualState>
                            <VisualState x:Name="Empty">
                                <VisualState.Setters>
                                    <Setter Target="HeaderContentPresenter.(UIElement.RenderTransform).(CompositeTransform.TranslateY)"
                                            Value="31" />
                                    <Setter Target="HeaderContentPresenter.(UIElement.RenderTransform).(CompositeTransform.TranslateX)"
                                            Value="11" />
                                </VisualState.Setters>
                                <VisualState.StateTriggers>
                                    <StateTrigger IsActive="{Binding Text, Converter={StaticResource IsEmptyConverter}, Mode=OneWay, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                                </VisualState.StateTriggers>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="BorderElement"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Background="{TemplateBinding Background}"
                            Grid.ColumnSpan="2"
                            Grid.Row="1"
                            Grid.RowSpan="1" />
                    <ContentPresenter x:Name="HeaderContentPresenter"
                                        Grid.ColumnSpan="2"
                                        ContentTemplate="{TemplateBinding HeaderTemplate}"
                                        Content="{TemplateBinding Header}"
                                        Foreground="{ThemeResource TextControlHeaderForeground}"
                                        FontWeight="Normal"
                                        Margin="0,0,0,8"
                                        Grid.Row="0"
                                        Visibility="Collapsed"
                                        x:DeferLoadStrategy="Lazy"
                                        RenderTransformOrigin="0.5,0.5">
                        <ContentPresenter.RenderTransform>
                            <CompositeTransform />
                        </ContentPresenter.RenderTransform>
                    </ContentPresenter>
                    <ScrollViewer x:Name="ContentElement"
                                    AutomationProperties.AccessibilityView="Raw"
                                    HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
                                    HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
                                    IsTabStop="False"
                                    IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
                                    IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
                                    IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
                                    Margin="{TemplateBinding BorderThickness}"
                                    Padding="{TemplateBinding Padding}"
                                    Grid.Row="1"
                                    VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
                                    VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
                                    ZoomMode="Disabled" />
                    <ContentControl x:Name="PlaceholderTextContentPresenter"
                                    Grid.ColumnSpan="2"
                                    Content="{TemplateBinding PlaceholderText}"
                                    Foreground="{ThemeResource TextControlPlaceholderForeground}"
                                    IsHitTestVisible="False"
                                    IsTabStop="False"
                                    Margin="{TemplateBinding BorderThickness}"
                                    Padding="{TemplateBinding Padding}"
                                    Grid.Row="1" />
                    <Button x:Name="DeleteButton"
                            AutomationProperties.AccessibilityView="Raw"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Grid.Column="1"
                            FontSize="{TemplateBinding FontSize}"
                            IsTabStop="False"
                            Margin="{ThemeResource HelperButtonThemePadding}"
                            MinWidth="34"
                            Grid.Row="1"
                            Style="{StaticResource DeleteButtonStyle}"
                            Visibility="Collapsed"
                            VerticalAlignment="Stretch" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The converter is very simple. It has an extra IsInvertered property to invert a True to a False.

public class EmptyStringToTrueConverter : IValueConverter {
    public bool IsInverted { get; set; }

    public object Convert(object value, Type targetType, object parameter, string language) {
        var txt = (string)value;
        return string.IsNullOrEmpty(txt) ^ IsInverted;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language) {
        return DependencyProperty.UnsetValue;
    }
}

The converteres are registered using the following XAML.

<local:EmptyStringToTrueConverter x:Key="IsNotEmptyConverter" IsInverted="True" />
<local:EmptyStringToTrueConverter x:Key="IsEmptyConverter" />

Usage

My example page which I used in the video is shown below. All TextBoxes have the Style property set to AnimatedHeaderedTextBoxStyle and off course a Header property. I have set the IsSpellCheckEnable property to False because I don't want spellchecking on names and email addresses.  

<Page x:Class="App121.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App121"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Width="300"
                    Margin="0,12"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Center">

            <TextBox Header="Firstname"
                     Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                     IsSpellCheckEnabled="False"
                     InputScope="PersonalFullName"/>

            <TextBox Header="Lastname"
                     Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                     IsSpellCheckEnabled="False"
                     Margin="0,8"
                     InputScope="PersonalFullName" />

            <TextBox Header="E-mail"
                     Style="{StaticResource AnimatedHeaderedTextBoxStyle}"
                     IsSpellCheckEnabled="False"
                     InputScope="EmailNameOrAddress" />

            <Button Content="Next"
                    Margin="0,16,0,0" />
        </StackPanel>

    </Grid>
</Page>

I have placed the AnimatedHeaderedTextBoxStyle, the registration of the converters and a DeleteButtonStyle into ResourceDictionary named AnimatedHeaderedTextBoxStyle.xaml. The DeleteButtonStyle was generated by Blend when I created the Style by 'Editing a Copy...' of the Template for an existing TextBox.

Edit a Copy

In have registered the ResourceDictionary from the App.xaml which makes it available for in all pages and controls in this application.

<Application
    x:Class="App121.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App121"
    RequestedTheme="Light">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="AnimatedHeaderedTextBoxStyle.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

The easiest way to add this Resource Dictionary as a MergedDictionaries in the App.xaml is to use Blend. Open the Resources Window and right click the App.xaml file. Then select 'Link to Resource Dictionary' and check the AnimatedHeaderedTextBoxStyle.xaml file.

Link to ResourceDictionary

The code

I have published my code on GitHub. I hope you like it.

Fons

XAML Storyboard.IsPlaying Attached Property

0 Comments
By Fons Sonnemans, 24-2-2017

XAML is very powerful. I have blogged about behaviors a lot. They are great but also have limitations. You can only drag them on Controls, not on Storyboards. To "fix" this I came up with a solution using Attached Properties. The designer support isn't as good with Behaviors but who needs that if we have IntelliSense.

IsPlaying Attached Property

I have created a StoryboardServices class which contains the IsPlaying attached property of the type boolean. In the OnIsPlayingChanged() method I Begin() or Resume() the Storyboard if it is set to True, I Pause() it when it is set to False.

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Animation;

namespace AnimationDemo {
    public class StoryboardServices {

        #region IsPlaying Attached Property

        /// <summary> 
        /// Identifies the IsPlaying attachted property. This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty IsPlayingProperty =
            DependencyProperty.RegisterAttached("IsPlaying",
                typeof(bool),
                typeof(StoryboardServices),
                new PropertyMetadata(false, OnIsPlayingChanged));

        /// <summary>
        /// IsPlaying changed handler. 
        /// </summary>
        /// <param name="d">FrameworkElement that changed its IsPlaying attached property.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param> 
        private static void OnIsPlayingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var source = d as Storyboard;
            if (source != null) {
                var value = (bool)e.NewValue;
                if (value) {
                    if (source.GetCurrentState() == ClockState.Stopped) {
                        source.Begin();
                    } else {
                        source.Resume();
                    }
                } else {
                    source.Pause();
                }
            }
        }

        /// <summary>
        /// Gets the value of the IsPlaying attached property from the specified FrameworkElement.
        /// </summary>
        public static bool GetIsPlaying(DependencyObject obj) {
            return (bool)obj.GetValue(IsPlayingProperty);
        }


        /// <summary>
        /// Sets the value of the IsPlaying attached property to the specified FrameworkElement.
        /// </summary>
        /// <param name="obj">The object on which to set the IsPlaying attached property.</param>
        /// <param name="value">The property value to set.</param>
        public static void SetIsPlaying(DependencyObject obj, bool value) {
            obj.SetValue(IsPlayingProperty, value);
        }

        #endregion IsPlaying Attached Property

    }
}

Example

In this example I have a Storyboard1 which plays when the checkbox on the page is checked. I databound the IsPlaying attached property to the IsChecked property of the checkbox (see line 10). The animation is really simple but you get the idea.

<Page x:Class="AnimationDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:AnimationDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>
        <Storyboard x:Name="Storyboard1"
             local:StoryboardServices.IsPlaying="{Binding IsChecked, ElementName=checkBoxIsAnimated, Mode=OneWay}"
             RepeatBehavior="Forever">
            <ColorAnimationUsingKeyFrames
                    Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
                   Storyboard.TargetName="myBorder">
                <EasingColorKeyFrame KeyTime="0:0:1"
                                     Value="Blue" />
                <EasingColorKeyFrame KeyTime="0:0:2"
                                     Value="Red" />
            </ColorAnimationUsingKeyFrames>
        </Storyboard>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>

        <CheckBox x:Name="checkBoxIsAnimated"
                  Content="Animate"
                  IsChecked="True"
                  VerticalAlignment="Top"
                  HorizontalAlignment="Center"
                  Grid.Column="1" />

        <Border x:Name="myBorder"
                Height="100"
                Grid.Column="1"
                Background="Red" />
    </Grid>
</Page>

The code

I have published my code on GitHub. I hope you like it.

Fons

XAML ScrollSelectedItemIntoViewBehavior

0 Comments
By Fons Sonnemans, 12-1-2017

I love C#, XAML and Blend. It is very powerful and lets me create powerful solutions. As a example of it's power I will demonstrate my ScrollSelectedItemIntoViewBehavior. It will let you scroll to a selected item into view of a ListView or GridView without having to write any code.

Demo

The following video (GIF) shows you how you can use it to scroll to the selected product into view of a ListView. This can be done animated or instant.

ScrollSelectedItemIntoViewBehavior Demo

My Solution

My solution is a Behavior which you can apply on any ListView or GridView using Blend for Visual Studio. I have included the Microsoft.Xaml.Behaviors.Uwp.Managed NuGet package to the project. The ScrollSelectedItemIntoViewBehavior class derives from the Behavior<T> class (from the NuGet package) in which T is a ListViewBase. This allows me to use (drop) the behavior on a ListView or GridView. In the OnAttached() and OnDetached() methods I subscribe/unsubscribe to the SelectionChanged event of the AssociatedObject, the ListView or GridView. In the AssociatedObject_SelectionChanged() method the AssociatedObject scrolls to the selected item. For the scrolling I use some code (extension methods) I found on StackOverflow which does the hard work. I only changed some naming of the methods to follow my own code conventions.

public class ScrollSelectedItemIntoViewBehavior : Behavior<ListViewBase> {

    protected override void OnAttached() {
        AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
        base.OnAttached();
    }
    protected override void OnDetaching() {
        AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
        base.OnDetaching();
    }

    private async void AssociatedObject_SelectionChanged(object sender, 
                                                    SelectionChangedEventArgs e) {
        var item = e.AddedItems.FirstOrDefault();
        if (item != null) {
            if (IsScrollAnimated) {
                await this.AssociatedObject.ScrollToItemAsync(item);
            } else {
                await this.AssociatedObject.ScrollIntoViewAsync(item);
            }
        }
    }

    /// <summary> 
    /// Get or Sets the IsScrollAnimated dependency property.  
    /// </summary> 
    public bool IsScrollAnimated {
        get { return (bool)GetValue(IsScrollAnimatedProperty); }
        set { SetValue(IsScrollAnimatedProperty, value); }
    }

    /// <summary> 
    /// Identifies the IsScrollAnimated dependency property. 
    /// This enables animation, styling, binding, etc...
    /// </summary> 
    public static readonly DependencyProperty IsScrollAnimatedProperty =
        DependencyProperty.Register(nameof(IsScrollAnimated),
                            typeof(bool),
                            typeof(ScrollSelectedItemIntoViewBehavior),
                            new PropertyMetadata(true));
}

 

Using the Behavior

My sample project also contains some SampleData (products) which I used to populate a ListView. I have dragged the ScrollSelectedItemIntoViewBehavior from the Assets panel and dropped it on the ListView. The behavior also contains an IsScrollAnimated dependency property. The ToggleSwitch is databound to this property.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App47"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:Behaviors="using:App47.Behaviors"
      x:Class="App47.MainPage"
      mc:Ignorable="d">

    <Page.Resources>
        <DataTemplate x:Key="ProductTemplate">
            <Grid Height="80"
                  Margin="0,4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Image Source="{Binding ImageUrl}"
                       Height="80"
                       Width="80" />
                <StackPanel Grid.Column="1"
                            Margin="8,0,0,0">
                    <TextBlock Text="{Binding Name}"
                               Style="{StaticResource TitleTextBlockStyle}" />
                    <TextBlock Text="{Binding Price}"
                               Style="{StaticResource CaptionTextBlockStyle}"
                               TextWrapping="NoWrap" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          DataContext="{Binding Source={StaticResource SampleDataSource}}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="380*" />
            <ColumnDefinition Width="1541*" />
        </Grid.ColumnDefinitions>
        <StackPanel>
            <Button Content="First"
                    Click="ButtonFirst_Click"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="4" />
            <Button Content="Middle"
                    Click="ButtonMiddle_Click"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="4" />
            <Button Content="Last"
                    Click="ButtonLast_Click"
                    HorizontalAlignment="Stretch"
                    VerticalAlignment="Stretch"
                    Margin="4" />
            <ToggleSwitch Header="IsAnimated"
                          Margin="4"
                          IsOn="{x:Bind myBehavior.IsScrollAnimated, Mode=TwoWay}" />
        </StackPanel>
        <ListView x:Name="listViewProducts"
                  Grid.Column="1"
                  ItemTemplate="{StaticResource ProductTemplate}"
                  ItemsSource="{Binding Products}">
            <Interactivity:Interaction.Behaviors>
                <Behaviors:ScrollSelectedItemIntoViewBehavior x:Name="myBehavior" />
            </Interactivity:Interaction.Behaviors>
        </ListView>
    </Grid>
</Page>

The buttons click eventhandlers only set the SelectedIndex of the ListView. An MVVM solution in which the SelectedIndex is databound to a property of a model would also work.

public sealed partial class MainPage : Page {

    public MainPage() {
        this.InitializeComponent();
    }

    private void ButtonFirst_Click(object sender, RoutedEventArgs e) {
        listViewProducts.SelectedIndex = 0;
    }

    private void ButtonMiddle_Click(object sender, RoutedEventArgs e) {
        listViewProducts.SelectedIndex = (listViewProducts.Items.Count - 1) / 2;
    }

    private void ButtonLast_Click(object sender, RoutedEventArgs e) {
        listViewProducts.SelectedIndex = listViewProducts.Items.Count - 1;

    }
}

The code

I have published my code on GitHub. I hope you like it.

Fons

Windows XAML Tips - ResourceDictionary with CodeBehind

0 Comments
By Fons Sonnemans, 17-4-2015

I have created XAML solutions (WPF, Silverlight, Windows Phone, Windows 8) for about 7 years now. This week I learned a new feature which I thought wasn't possible. I learned how you can place DataTemplates with eventhandlers into ResouceDictionaries. I thought this wasn't possible because ResourceDictionaries don't have CodeBehind files to write the eventhandlers in. But I was wrong. You can add a CodeBehind to a ResourceDictionary but you will have to do it manually. This makes it possible to move more DataTemplates and ControlTemplates from Pages and UserControls to ResourceDictionaries which can help to avoid duplicate XAML code.

Demo App

My demo app is a Windows app developed using the MVVM design pattern. It contains a MainPage (view), a MainViewModel (viewmodel) class and a Product (model) class. The MainViewModel has a Products property which is a list of 3 hard coded products. The MainPage has a ListView which ItemsSource is DataBound to the Products property of the MainViewModel. The ItemsTemplate of the ListView is set to the ProductsDataTemplate which is a static resource of the Page. This DataTemplate contains an Image control which has 2 events (PointerEntered and PointerExited). In this example I changed the Opacity of the image when you hover over it using your mouse. It is a silly implementation which could also have been implemented using Behaviors in Blend.

MainPage.xaml

<Page x:Class="ResourceDictionaryWithCodeBehindDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ResourceDictionaryWithCodeBehindDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:vm="using:ResourceDictionaryWithCodeBehindDemo.ViewModels"
      mc:Ignorable="d">

    <Page.Resources>
        <DataTemplate x:Key="ProductDataTemplate">
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding ImageUrl}"
                       PointerEntered="Image_PointerEntered"
                       PointerExited="Image_PointerExited"
                       Opacity="0.3"
                       Width="100"
                       Height="100" />
                <StackPanel Margin="20,0">
                    <TextBlock Text="{Binding Name}" 
                               FontSize="30"/>
                    <TextBlock Text="{Binding Price}"
                               FontSize="20" />
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </Page.Resources>

    <Page.DataContext>
        <vm:MainViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView Margin="50"
                  ItemsSource="{Binding Products}"
                  ItemTemplate="{StaticResource ProductDataTemplate}">
        </ListView>
    </Grid>
</Page>

The MainPage.xaml.cs file contains the MainPage class with the Image_PointerEntered and Image_PointerExited eventhandlers. They change the Opacity of the Image (sender).

public sealed partial class MainPage : Page {
    public MainPage() {
        this.InitializeComponent();
    }

    private void Image_PointerEntered(object sender, PointerRoutedEventArgs e) {
        var img = sender as Image;
        img.Opacity = 1.0;
    }

    private void Image_PointerExited(object sender, PointerRoutedEventArgs e) {
        var img = sender as Image;
        img.Opacity = 0.3;
    }
}

The MainViewModel.cs file contains the MainViewModel class which has the Products property. The Constructor adds 3 sample products to the List.

using System;
using ResourceDictionaryWithCodeBehindDemo.Models;
using System.Collections.Generic;

namespace ResourceDictionaryWithCodeBehindDemo.ViewModels {
    class MainViewModel {

        public List<Product> Products { get; set; }

        public MainViewModel() {
            this.Products = new List<Product>() {
                new Product { Name = "Computer", Price = 2000},
                new Product { Name = "Car", Price = 34000},
                new Product { Name = "Table", Price = 500},
            };
        }
    }
}

The Product.cs file contains the Product class with the Name, Price and ImageUrl properties.

using System;

namespace ResourceDictionaryWithCodeBehindDemo.Models {
    class Product {

        public string Name { get; set; }
        public double Price { get; set; }

        public string ImageUrl {
            get {
                return "http://lorempixel.com/150/150/technics/" + Name;
            }
        }
    }
}

Add ResourceDictionary

Now you can add a ResourceDictionary to the project. Created a folder Resources in the root of the Project in which you will add the ResourceDictionary file. Create this file in Blend and not in Visual Studio because Blend will add the MergedDictionary code to my App.xaml. I named my file MyDataTemplates.xaml.

Add new Item in Blend

MyDataTemplates.xaml will look like this.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ResourceDictionaryWithCodeBehindDemo">
    
</ResourceDictionary>

Blend will also add the ResourceDictionary to the MergedDictionary of your Application in the  App.xaml.

<Application
    x:Class="ResourceDictionaryWithCodeBehindDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ResourceDictionaryWithCodeBehindDemo">
    
	<Application.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceDictionary Source="Resources/MyDataTemplates.xaml"/>
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</Application.Resources>

</Application>

Add CodeBehind to ResourceDictionary

Now you can create the MyDataTemplates.xaml.cs file in the Resources folder of the project. I do this in Visual Studio and not in Blend. In Visual Studio I have installed an the File Nesting extion (created by Mads Kristensen). This extension will automatically nest the codefile in the xaml file. It is not necessary but I like a clean solution. The complete structure of my Solution looks likes this:

Solution Explorer

Now you must add an x:Class attribute to the ResourceDictionary element in the MyDataTemplates.xaml file. This attribute points to the ResourceDictionaryWithCodeBehindDemo.Resources.MyDataTemplate class. Next you can move the ProductDataTemplate from the MainPage to the ResourceDictionary.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="ResourceDictionaryWithCodeBehindDemo.Resources.MyDataTemplates"
        xmlns:local="using:ResourceDictionaryWithCodeBehindDemo">

    <DataTemplate x:Key="ProductDataTemplate">
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding ImageUrl}"
                   PointerEntered="Image_PointerEntered"
                   PointerExited="Image_PointerExited"
                   Opacity="0.3"
                   Width="100"
                   Height="100" />
            <StackPanel Margin="20,0">
                <TextBlock Text="{Binding Name}"
                           FontSize="30" />
                <TextBlock Text="{Binding Price}"
                           FontSize="20" />
            </StackPanel>
        </StackPanel>
    </DataTemplate>

</ResourceDictionary>

The MyDataTemplate class must be a partial class. You must also add a constructor (use the ctor snippet) to the class and call InitializeComponent(). This InitializeComponent method will be automatically generated if you set the x:Class attribute in the XAML. Finally you can move the eventhandlers from the MainPage to the MyDataTemplates class.

using System;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;

namespace ResourceDictionaryWithCodeBehindDemo.Resources {
    partial class MyDataTemplates {

        public MyDataTemplates() {
            this.InitializeComponent();
        }

        private void Image_PointerEntered(object sender, PointerRoutedEventArgs e) {
            var img = sender as Image;
            img.Opacity = 1.0;
        }

        private void Image_PointerExited(object sender, PointerRoutedEventArgs e) {
            var img = sender as Image;
            img.Opacity = 0.3;
        }
    }
}

The App.xaml must also be changed. You will have to register an extra xml namespace named res which holds the 'using:ResourceDictionaryWithCodeBehindDemo.Resources' value. In the MergedDictionaries element you must remove the MyDataTemplates ResourceDictionary and add an <res:MyDataTemplates /> element. This will instantiate an instance of the CodeBehind class. The constructor of the class will load the XAML in the InitializeComponent() method.

<Application
    x:Class="ResourceDictionaryWithCodeBehindDemo.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    
    xmlns:res="using:ResourceDictionaryWithCodeBehindDemo.Resources"
    
    xmlns:local="using:ResourceDictionaryWithCodeBehindDemo">
    
	<Application.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<!--<ResourceDictionary Source="Resources/MyDataTemplates.xaml"/>-->
                <res:MyDataTemplates />
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</Application.Resources>

</Application>

The MainPage.xaml now doesn't have the ProductsDataTemplate.

<Page x:Class="ResourceDictionaryWithCodeBehindDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ResourceDictionaryWithCodeBehindDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:vm="using:ResourceDictionaryWithCodeBehindDemo.ViewModels"
      mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainViewModel />
    </Page.DataContext>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView Margin="50"
                  ItemsSource="{Binding Products}"
                  ItemTemplate="{StaticResource ProductDataTemplate}">
        </ListView>
    </Grid>
</Page>

MainPage.xaml.cs now only contains the constructor.

public sealed partial class MainPage : Page {

    public MainPage() {
        this.InitializeComponent();
    }
}

Closure and download

I hope you like my solution it will open up a lot of extra possibilities to structure your XAML projects. You can download the sample project below.

Cheers,

Fons

Tags: XAML, Blend, Apps, CSharp

Windows 8 XAML Tips - Trigger Behaviors

0 Comments
By Fons Sonnemans, 2-7-2014

I use Behaviors in my XAML apps all the time. I have already written a few blog post about this subject. In Silverlight and WPF there was a clear distinction between Actions, Triggers and Behaviors. Triggers are used to invoke an Action. You can use the EventTrigger, StoryBoardCompleteTrigger, KeyTrigger, TimerTrigger, PropertyChangedTrigger, DataTrigger and DataStoreTrigger. And you can easily write your own by using the Trigger 'New Item' template in Blend.

In the Windows 8.1 'Behavior SDK' the Triggers are replaced by Trigger Behaviors. You only get the DataTriggerBehavior and EventTriggerBehavior but you can write your own. With this blog post I will try to explain how to do this. I will use a TimerTriggerBehavior and a SwipeTriggerBehavior which you can use to execute actions when you active a swipe gesture on a UIElement.

TimerTriggerBehavior

Before I can explain my TimerTriggerBehavior I first have to re-introduce my Behavior<T> class which I use as a base class for all my behaviors.

public abstract class Behavior<T> : DependencyObject, IBehavior 
    where T : DependencyObject {

    [EditorBrowsable(EditorBrowsableState.Never)]
    public T AssociatedObject { get; set; }

    protected virtual void OnAttached() {
    }

    protected virtual void OnDetaching() {
    }

    public void Attach(DependencyObject associatedObject) {
        if (associatedObject == this.AssociatedObject || 
            DesignMode.DesignModeEnabled) {
            return;
        }

        this.AssociatedObject = (T)associatedObject;
        OnAttached();
    }

    public void Detach() {
        if (!DesignMode.DesignModeEnabled) {
            OnDetaching();
        }
    }

    DependencyObject IBehavior.AssociatedObject {
        get { return this.AssociatedObject; }
    }
}

The TimerTriggerBehavior class is derives from this Behavior<T> class and contains DispatcherTimer. It has a MilliSecondsPerTick and IsEnabled properties. The MilliSecondsPerTick property is used to set the Interval of the Property. The IsEnabled property is used to start and stop the timer. It is an DependencyProperty so you can DataBind it. In Tick event is subscribed in the OnAttached() method and unsubscribed in the OnDetached() method. The Timer_Tick() event handler calls the Execute() method which invokes all triggers. The class also contains an Actions DependencyProperty and a Execute() method and the class is decorated with the ContentPropertyAttribute. This will be used by Blend for Visual Studio to allow Drag&Drop of Actions onto the Behavior.

[ContentProperty(Name = "Actions")]
public class TimerTriggerBehavior : Behavior<DependencyObject> {

    private DispatcherTimer _timer = new DispatcherTimer();

    private int _millisecondPerTick = 1000;

    public int MilliSecondsPerTick {
        get { return _millisecondPerTick; }
        set {
            _millisecondPerTick = value;
            _timer.Interval = TimeSpan.FromMilliseconds(_millisecondPerTick);
        }
    }

    #region Actions Dependency Property

    /// <summary> 
    /// Actions collection 
    /// </summary> 
    public ActionCollection Actions {
        get {
            var actions = (ActionCollection)base.GetValue(ActionsProperty);
            if (actions == null) {
                actions = new ActionCollection();
                base.SetValue(ActionsProperty, actions);
            }
            return actions;
        }
    }

    /// <summary> 
    /// Backing storage for Actions collection 
    /// </summary> 
    public static readonly DependencyProperty ActionsProperty =
        DependencyProperty.Register("Actions",
                                    typeof(ActionCollection),
                                    typeof(SwipeTriggerBehavior),
                                    new PropertyMetadata(null));

    #endregion Actions Dependency Property

    protected void Execute(object sender, object parameter) {
        Interaction.ExecuteActions(sender, this.Actions, parameter);
    }

    protected override void OnAttached() {
        base.OnAttached();
        _timer.Tick += timer_Tick;
        if (this.IsEnabled) {
            _timer.Start();
        }
    }

    private void timer_Tick(object sender, object e) {
        this.Execute(this, null);
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        _timer.Stop();
        _timer.Tick -= timer_Tick;
    }

    #region IsEnabled Dependency Property

    /// <summary> 
    /// Get or Sets the IsEnabled dependency property.  
    /// </summary> 
    public bool IsEnabled {
        get { return (bool)GetValue(IsEnabledProperty); }
        set { SetValue(IsEnabledProperty, value); }
    }

    /// <summary> 
    /// Identifies the IsEnabled dependency property. 
    /// This enables animation, styling, binding, etc...
    /// </summary> 
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.Register("IsEnabled",
                typeof(bool),
                typeof(TimerTriggerBehavior),
                new PropertyMetadata(true, OnIsEnabledPropertyChanged));

    /// <summary>
    /// IsEnabled changed handler. 
    /// </summary>
    /// <param name="d">TimerTriggerBehavior that changed its IsEnabled.</param>
    /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
    private static void OnIsEnabledPropertyChanged(DependencyObject d, 
                            DependencyPropertyChangedEventArgs e) {
        var source = d as TimerTriggerBehavior;
        if (source != null) {
            var value = (bool)e.NewValue;
            if (value) {
                source._timer.Start();
            } else {
                source._timer.Stop();
            }
        }
    }

    #endregion IsEnabled Dependency Property

SwipeTriggerBehavior

The SwipeTriggerBehavior has a Direction property which you can set the swipe direction which will trigger the Actions. The ManipulationCompleted event on the UIElement is used to detect the swipe gesture. The ManipulationCompletedRoutedEventArgs.Velocities.Linear.X and Y are used to check the direction. I use this Between() extension method to check it is between 0.3 and 100. These values work for me but maybe not for you. Maybe I should make these flexible using properties (next version).

[ContentProperty(Name = "Actions")]
public class SwipeTriggerBehavior : Behavior<UIElement> {

    /// <summary>
    /// Get/Sets the direction of the Swipe gesture 
    /// </summary>
    public SwipeDirection Direction { get; set; }

    #region Actions Dependency Property

    /// <summary> 
    /// Actions collection 
    /// </summary> 
    public ActionCollection Actions {
        get {
            var actions = (ActionCollection)base.GetValue(ActionsProperty);
            if (actions == null) {
                actions = new ActionCollection();
                base.SetValue(ActionsProperty, actions);
            }
            return actions;
        }
    }

    /// <summary> 
    /// Backing storage for Actions collection 
    /// </summary> 
    public static readonly DependencyProperty ActionsProperty =
        DependencyProperty.Register("Actions",
                                    typeof(ActionCollection),
                                    typeof(SwipeTriggerBehavior),
                                    new PropertyMetadata(null));

    #endregion Actions Dependency Property

    protected void Execute(object sender, object parameter) {
        Interaction.ExecuteActions(sender, this.Actions, parameter);
    }

    protected override void OnAttached() {
        base.OnAttached();

        this.AssociatedObject.ManipulationMode = 
            this.AssociatedObject.ManipulationMode | 
            ManipulationModes.TranslateX | 
            ManipulationModes.TranslateY;

        this.AssociatedObject.ManipulationCompleted += OnManipulationCompleted;
    }

    protected override void OnDetaching() {
        base.OnDetaching();
        this.AssociatedObject.ManipulationCompleted -= OnManipulationCompleted;
    }

    private void OnManipulationCompleted(object sender, 
                                        ManipulationCompletedRoutedEventArgs e) {

        bool isRight = e.Velocities.Linear.X.Between(0.3, 100);
        bool isLeft = e.Velocities.Linear.X.Between(-100, -0.3);

        bool isUp = e.Velocities.Linear.Y.Between(-100, -0.3);
        bool isDown = e.Velocities.Linear.Y.Between(0.3, 100);

        switch (this.Direction) {
            case SwipeDirection.Left:
                if (isLeft && !(isUp || isDown)) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.Right:
                if (isRight && !(isUp || isDown)) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.Up:
                if (isUp && !(isRight || isLeft)) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.Down:
                if (isDown && !(isRight || isLeft)) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.LeftDown:
                if (isLeft && isDown) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.LeftUp:
                if (isLeft && isUp) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.RightDown:
                if (isRight && isDown) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            case SwipeDirection.RightUp:
                if (isRight && isUp) {
                    this.Execute(this.AssociatedObject, null);
                }
                break;
            default:
                break;
        }
    }
}

public enum SwipeDirection {
    Left,
    Right,
    Up,
    Down,
    LeftDown,
    LeftUp,
    RightDown,
    RightUp,
}

This class also contains an Actions DependencyProperty and an Execute() method. Just like the TimerTrigger. I have tried to place them in an abstract base class TriggerBehavior<> but that caused problems in Blend. You couldn't drop an action on the Triggers deriving from this class. So I have chosen to have some duplicate code.

Triggers Demo Project

To demonstrate these behaviors I have written this 'Triggers Demo' project. I Blend for Visual Studio I have dropped a TimerTriggerBehavior onto the root Grid element of the MainPage.

On the TimerTriggerBehavior I have dropped a PlaySoundAction. The Source of this action is set to a Click.wav which I added to the \Assets\Sounds folder of the project.

The Grid also contains a ToggleSwitch. The IsOn property is databound to the IsEnabled property of the timer trigger.

<ToggleSwitch Header="TimeTrigger"
                HorizontalAlignment="Left"
                Margin="114,-6,0,0"
                Grid.Row="1"
                VerticalAlignment="Top"
                OnContent="Enabled"
                OffContent="Disabled"
                IsOn="{Binding IsEnabled, ElementName=MyTimerTrigger, Mode=TwoWay}" />

I have dropped the SwipeTriggerBehavior 4 times on a blue Grid. Each with a different Direction: Up, Down, Left, Right. Inside each trigger I have dropped a ChangePropertyAction. The Value of the Text property of a TextBlock is set to the direction

<Grid Grid.Row="2"
        Margin="120,0,40,40"
        Background="#0096FF">

    <Interactivity:Interaction.Behaviors>
        <Behaviors:SwipeTriggerBehavior Direction="Up">
            <Core:ChangePropertyAction PropertyName="Text"
                                        TargetObject="{Binding ElementName=tbDemo}"
                                        Value="Up" />
        </Behaviors:SwipeTriggerBehavior>
        <Behaviors:SwipeTriggerBehavior Direction="Down">
            <Core:ChangePropertyAction PropertyName="Text"
                                        TargetObject="{Binding ElementName=tbDemo}"
                                        Value="Down" />
        </Behaviors:SwipeTriggerBehavior>
        <Behaviors:SwipeTriggerBehavior Direction="Left">
            <Core:ChangePropertyAction PropertyName="Text"
                                        TargetObject="{Binding ElementName=tbDemo}"
                                        Value="Left" />
        </Behaviors:SwipeTriggerBehavior>
        <Behaviors:SwipeTriggerBehavior Direction="Right">
            <Core:ChangePropertyAction PropertyName="Text"
                                        TargetObject="{Binding ElementName=tbDemo}"
                                        Value="Right" />
        </Behaviors:SwipeTriggerBehavior>
    </Interactivity:Interaction.Behaviors>

    <TextBlock Margin="50"
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Style="{StaticResource HeaderTextBlockStyle}"
                Text="Swipe Up, Down, Left or Right" />

    <TextBlock x:Name="tbDemo"
                HorizontalAlignment="Center"
                VerticalAlignment="Center"
                Style="{StaticResource HeaderTextBlockStyle}" />

</Grid>

The result is a Page in which you hear a click sound play every 2 seconds. You can turn the sound off using the ToggleSwitch. If you swipe the blue box the swipe direction is shown in the center of this box.

Closure and download

I hope you like my solution. Feel free to use it in your projects. You can download the sample project below.

Cheers,

Fons

Windows 8 XAML Tips - Combining Converters

1 Comments
By Fons Sonnemans, 23-12-2013

XAML is a declarative UI language; it's most powerful feature is surely data binding. In XAML you can set a converter on any binding. You use this when you need to display data in a format that differs from how it is stored. But you can only set ONE converter. So if you for instance want to convert a date using a FormatConverter and you want to convert the result to uppercase using a UppercaseConverter you are stuck. To solve this problem I have created a ValueConverterGroup class. It is a converter which contains multiple converters, it is a List of IValueConverter objects and it implements the IValueConverter interface. The Convert() method calls the Convert() method of all containing value converters. I haven't implement the ConvertBack() method because I won't be using it.

ValueConverterGroup

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace ReflectionIT.Windows8.Converters {

    public class ValueConverterGroup : List<IValueConverter>, IValueConverter {

        public object Convert(object value, Type targetType, object parameter, 
                                  string language) {
            return this.Aggregate(value, (current, converter) =>
                current == DependencyProperty.UnsetValue ? 
                    current : 
                    converter.Convert(current, targetType, parameter, language)
            );
        }

        public object ConvertBack(object value, Type targetType, object parameter, 
                                  string language) {
            System.Diagnostics.Debugger.Break(); // Not Implemented
            throw new NotImplementedException();
        }
    }
}

Example

The following sample page contains a DatePicker control and two TextBlock controls. The Text property of the TextBlocks are databound to the Date property of the DatePicker. The first (red) TextBlock uses a FormatConverter to convert the date to dd-MMM-yyyy string. The second (blue) TextBlock uses my ValueConverterGroup to combine a FormatConverter and an UppercaseConverter.

The two converters are static resources which are defined in the Page resources.

<Page x:Class="ConvertersDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:ConvertersDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:c="using:ReflectionIT.Windows8.Converters"
      mc:Ignorable="d">


    <Page.Resources>
        <c:FormatConverter x:Key="DateFormat"
                           FormatString="{}{0:dd-MMM-yyyy}" />

        <c:ValueConverterGroup x:Key="UppercaseDateFormat">
            <c:FormatConverter FormatString="{}{0:dd-MMM-yyyy}" />
            <c:UppercaseConverter />
        </c:ValueConverterGroup>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <DatePicker x:Name="datePicker"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Margin="136,85,0,0" />
        <TextBlock HorizontalAlignment="Left"
                   TextWrapping="Wrap"
                   Text="{Binding Date, Converter={StaticResource DateFormat},
                                   ElementName=datePicker}"
                   VerticalAlignment="Top"
                   Margin="136,164,0,0"
                   FontSize="64"
                   Foreground="Red" />
        <TextBlock HorizontalAlignment="Left"
                   TextWrapping="Wrap"
                   Text="{Binding Date, Converter={StaticResource UppercaseDateFormat},
                                   ElementName=datePicker}"
                   VerticalAlignment="Top"
                   Margin="136,283,0,0"
                   FontSize="64"
                   Foreground="Blue" />
    </Grid>
</Page>

As you can see in the next screenshot the blue TextBlock is showing the date in a dd-MMM-yyyy format in uppercase.

CombiningConverters

FormatConverter

The code of the FormatConverter class used in the example.

using System;
using System.Globalization;
using Windows.UI.Xaml.Data;

namespace ReflectionIT.Windows8.Converters {

    public class FormatConverter : IValueConverter {

        public string FormatString { get; set; }

        public object Convert(object value, Type typeName, object parameter, 
                                   string language) {

            string formatterString = (parameter as String) ?? FormatString;

            if (!string.IsNullOrEmpty(formatterString)) {
                return string.Format(formatterString, value);
            }

            return System.Convert.ToString(value);
        }

        public object ConvertBack(object value, Type typeName, object parameter, 
                                  string language) {
            System.Diagnostics.Debugger.Break(); // Not Implemented
            throw new NotImplementedException();
        }

    }
}

UppercaseConverter

The code of the UppercaseConverter class used in the example.

using System;
using Windows.UI.Xaml.Data;

namespace ReflectionIT.Windows8.Converters {

    class UppercaseConverter : IValueConverter {
        public object Convert(object value, Type targetType, object parameter, 
                                   string language) {
            return System.Convert.ToString(value).ToUpper();
        }

        public object ConvertBack(object value, Type targetType, object parameter, 
                                   string language) {
            System.Diagnostics.Debugger.Break(); // Not Implemented
            throw new NotImplementedException();
        }
    }
}

 

Closure and download

I hope you like my solution. Combining multiple converters is now very easy. You can download my code below.

Cheers,

Fons

Windows 8 XAML Tips - Popup.ShowAsync()

2 Comments
By Fons Sonnemans, 12-11-2013

In XAML applications you can use a Popup control to displays content on top of existing content. To show a Popup you must set the IsOpen property to true. You can subscribe yourself to the Closed eventhandler which is fired when the IsOpen property is (re)set to false. You can use this event to execute extra logic when the Popup is closed.

To make this easier I have created an ShowAsync() extension method which does this for you. I got my inspiration for this solution from this Awaiting Storyboard completion blog post by Nigel Sampson. He uses this technique to begin a storyboard using a BeginAsync() extension method.

private async void ButtonShowAsync_Click(object sender, RoutedEventArgs e) {
    (sender as Button).Content = "Clicked at " + DateTime.Now.ToString("HH:mm:ss");
    await this.popupTest.ShowAsync();
    (sender as Button).Content = "Closed at " + DateTime.Now.ToString("HH:mm:ss");
}

private void ButtonPopupIsOpen_Click(object sender, RoutedEventArgs e) {
    (sender as Button).Content = "Clicked at " + DateTime.Now.ToString("HH:mm:ss");
    this.popupTest.IsOpen = true;
    // You don't know when the popup was closed!
}

The ShowAsync() extension method uses an TaskCompletionSource. It allows us to create tasks behind the scenes and control whether they are completed, cancelled or failed. With this task you can build up some really handy mechanisms to improve the readability of your code.

static class Extensions {

    public static Task ShowAsync(this Popup popup) {

        TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

        EventHandler<object> onClosed = null;
        onClosed = (s, e) => {
            popup.Closed -= onClosed;
            tcs.SetResult(true);
        };
        popup.Closed += onClosed;
        popup.IsOpen = true;

        return tcs.Task;
    }

}

The XAML used for this demo.

<Page x:Class="App4.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App4"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Margin="20">

            <Button Content="Popup.IsOpen"
                    Click="ButtonPopupIsOpen_Click" />

            <Button Content="Popup.ShowAsync"
                    Click="ButtonShowAsync_Click" />

        </StackPanel>

        <Popup x:Name="popupTest"
               Margin="150"
               IsLightDismissEnabled="True">
            <Grid Background="Red"
                  Width="200"
                  Height="200">
                <Button Content="Close"
                        HorizontalAlignment="Center"
                        Click="ButtonClose_Click" />
            </Grid>
        </Popup>
    </Grid>
</Page>

ExtensionMethod.net website

I have published this extension method on www.extensionmethod.net. It is a website where c#, vb.net, f# and javascript extension methods are shared. You can also find my Chooser.ShowAsync() extension method which you can use to show a Windows Phone Chooser (AddressChooserTask, EmailAddressChooserTask, PhoneNumberChooserTask, PhotoChooserTask) using this technique.

private async void Button_Click(object sender, RoutedEventArgs e) {
    try {
        var c = new PhotoChooserTask();
        var result = await c.ShowAsync();
        var bi = new BitmapImage();
        bi.SetSource(result.ChosenPhoto);
        imagePhoto.Source = bi;
    } catch (Exception ex) {
        MessageBox.Show(ex.Message);
    }
}

Closure and download

I hope you like my solution. My demo is writen as a Windows Store app but you can also use it in Windows Phone, Silverlight and WPF. You can download my code below.

Cheers,

Fons

Windows 8 XAML Tips - Conditional Behaviors

2 Comments
By Fons Sonnemans, 21-10-2013

A few weeks ago I have written a blog post about how you can write your own Behaviors and Actions for Windows 8.1. I noticed that the Windows 8.1 Actions are not compatible with the Silverlight and Windows Phone Actions. Those actions have a IsEnabled property.

The Windows 8.1 Actions don't have an IsEnabled property.

Luckily I got a tip from the Microsoft XAML Tools team. A new feature that is added to the Behaviors SDK is that Actions can return results as well as have its own ActionLists. This helps in relaying execution results or building conditional behaviors. 

IsEnabledCondition

A have written a simple class called IsEnabledCondition. This class implements the IAction interface which makes it an Action. It has two dependency properties: IsEnabled and Actions. The Actions property is set as the ContentProperty using the ContentPropertyAttribute on the class. The Execute() method only executes the contained actions when the IsEnabled property is set. I learned this ActionList from reading Mark Smith Behaviors in Windows 8.1 Store Apps blog post.

using System;
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;

namespace ConditionalBehaviorsDemo.Behaviors {

    [ContentProperty(Name = "Actions")]
    public class IsEnabledCondition : DependencyObject, IAction {


        #region IsEnabled Dependency Property

        /// <summary> 
        /// Get or Sets the IsEnabled dependency property.  
        /// </summary> 
        public bool IsEnabled {
            get { return (bool)GetValue(IsEnabledProperty); }
            set { SetValue(IsEnabledProperty, value); }
        }

        /// <summary> 
        /// Identifies the IsEnabled dependency property. 
        /// This enables animation, styling, binding, etc...
        /// </summary> 
        public static readonly DependencyProperty IsEnabledProperty =
            DependencyProperty.Register("IsEnabled",
                                        typeof(bool),
                                        typeof(IsEnabledCondition),
                                        new PropertyMetadata(true));

        #endregion IsEnabled Dependency Property

        #region Actions Dependency Property

        /// <summary>
        /// Actions collection
        /// </summary>
        public ActionCollection Actions {
            get {
                var actions = (ActionCollection)base.GetValue(ActionsProperty);
                if (actions == null) {
                    actions = new ActionCollection();
                    base.SetValue(ActionsProperty, actions);
                }
                return actions;
            }
        }

        /// <summary>
        /// Backing storage for Actions collection
        /// </summary>
        public static readonly DependencyProperty ActionsProperty =
            DependencyProperty.Register("Actions", 
                                        typeof(ActionCollection), 
                                        typeof(IsEnabledCondition), 
                                        new PropertyMetadata(null));

        #endregion Actions Dependency Property

        public object Execute(object sender, object parameter) {
            if (this.IsEnabled) {
                return Interaction.ExecuteActions(sender, this.Actions, parameter);
            }
            return null;
        }
    }
}

I use this action as the parent of my previously used ControlStoryBoardAction. The IsEnabled property is databound to the IsOn property of the ToggleSwitch. Databinding is possible because the IsEnabled property is an Dependency Property. If you start the application you can click the button it moves to the right and then comes back. If you turn the 'Animate' switch Off an then click the button nothing happens.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Button x:Name="button"
            Content="Button"
            HorizontalAlignment="Left"
            Height="97"
            Margin="90,72,0,0"
            VerticalAlignment="Top"
            Width="194"
            RenderTransformOrigin="0.5,0.5">
        <Button.RenderTransform>
            <CompositeTransform />
        </Button.RenderTransform>
        <Interactivity:Interaction.Behaviors>
            <Core:EventTriggerBehavior EventName="Click">
                <Behaviors:IsEnabledCondition 
                    IsEnabled="{Binding IsOn, ElementName=toggleSwitchAnimate}">
                    <Media:ControlStoryboardAction 
                        Storyboard="{StaticResource Storyboard1}" />
                </Behaviors:IsEnabledCondition>
            </Core:EventTriggerBehavior>
        </Interactivity:Interaction.Behaviors>
    </Button>
    <ToggleSwitch x:Name="toggleSwitchAnimate"
                    Header="Animate"
                    HorizontalAlignment="Left"
                    Margin="87,220,0,0"
                    VerticalAlignment="Top"
                    IsOn="True" />

</Grid>

Closure and download

I hope you like my solution. Maybe you can use it in your apps too. I plan to write many more conditional actions so check out my blogs for more posts. You can download my code below.

Cheers,

Fons

Windows Phone Week - Slides

0 Comments
By Fons Sonnemans, 8-10-2013

Op 5 oktober heb ik een 'Designing WP apps UI using Blend' presentatie verzorgt op de Windows Phone Developer Day. Deze dag was perfect georganiseerd door Joost van Schaik, Tom Verhoeff en Dennis Vroegop. Er waren ruim 60 deelnemers en de locatie bij Macaw was natuurlijk top. Meer info op http://www.dotned.nl/register/66/zaterdag-5-oktober--windows-phone-developer-day-.aspx

De slides zijn via onderstaande download link te bekijken. Voor vragen kan je natuurlijk altijd contact met mij opnemen.

Fons

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.