Blog

posts tagged with behaviors

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

XAML CalculatorBehavior

0 Comments
By Fons Sonnemans, 1-12-2016

I have been using Adobe software recently and I noticed you could do simple calculations in textboxes. I used it to export Tile Images in different scale sizes. If the 100% scale of an Image is 150 pixels wide you can enter '150 * 1.5'. It will calculate the width of 225 pixels for the 150% scale size. I loved this feature so I tried to implement it also for my own Xaml apps.

The solution is quite simple. I have created a Behavior called CalculatorBehavior. You just use Blend for Visual Studio to drop it on a TextBox control and you are done.

Demo App

The following video shows you how you can use it to do simpel calculations in a TextBox using the CalculatorBehavior.

CalculatorBehavior Demo

My Solution

My Visual Studio solution contains 3 projects: Windows 10 (UWP), Windows 8.1 and WPF. The WPF project has a NuGet reference to Math Expression Evaluator. This project contains the ExpressionEvaluator class which I use for my calculations. There is no WinRT, PCL (Portable Class Library) or .NET Standard implementation for this project which makes it unusable for my Windows (8.1 and 10/UWP) projects. Luckily the project is Open Sources so I added the ExpressionEvaluator class to these projects.

The Windows 10 project uses the XamlBehaviors project using the NuGet package. The Windows 8.1 project uses the 'Behaviors SDK' but that doesn't include a Behavior<T> class. So i added my own implementation for it in my project. The WPF project has a reference to the 'System.Windows.Interactivity' assembly.

CalculatorBehavior

The CalculatorBehavior code is very simple. The class derives from Behavior<TextBox> which makes it droppable on a TextBox. The LostFocus event of the TextBox will trigger the calculation for which I use the ExpressionEvaluator.

using Microsoft.Xaml.Interactivity;
using SimpleExpressionEvaluator;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace CalculatorUwpDemoApp.Behaviors {

    class CalculatorBehavior : Behavior<TextBox> {

        private static readonly ExpressionEvaluator _evaluator = 
            new ExpressionEvaluator();

        protected override void OnAttached() {
            base.OnAttached();
            this.AssociatedObject.LostFocus += Evaluate;
        }

        protected override void OnDetaching() {
            base.OnDetaching();
            this.AssociatedObject.LostFocus -= Evaluate;
        }

        private void Evaluate(object sender, RoutedEventArgs e) {
            try {
                string txt = this.AssociatedObject.Text;
                if (!string.IsNullOrWhiteSpace(txt)) {
                    var result = _evaluator.Evaluate(txt);
                    this.AssociatedObject.Text = result.ToString();
                }
            } catch { }
        }
    }
}

Blend for Visual Studio

Adding the CalculatorBehavior to a TextBox is very easy in Blend for Visual Studio. Select the behavior from the Assets panel and Drag&Drop it on a TextBox. That's all.

Blend Screenshot

The code

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

Fons

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 - 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 8 XAML Tips - Creating Blend Behaviors

0 Comments
By Fons Sonnemans, 16-9-2013

The new version of Blend for Visual Studio 2013 RC now supports Behaviors. I have used Behaviors for developing Silverlight and Windows Phone applications a lot. Now you can use them for you Windows Store XAML apps too. Two type of Behaviors are supported: Behaviors and Actions. Triggers are "dropped" and can/should now be implemented using Behaviors. 

There are 7 build-in Actions: CallMethodAction, ChangePropertyAction, ControlStoryboardAction, GoToStateAction, InvokeCommandAction, NavigateToPageAction and PlaySoundAction. Many were already available in Silverlight. The NavigateToPageAction is new.

There are 3 build-in Behaviors: DataTriggerBehavior, EventTriggerBehavior and IncrementalUpdateBehavior.

You can create your own Actions and Behaviors.  Before you can do that you must add a reference to the 'Behaviors SDK'.

Creating Actions

You create an Action by adding a class to your project which derives from the DependencyObject class and implements the IAction interface. In Silverlight there was a TriggerAction base class, but that no longer exists. The IAction interface has only one Execute() method you should implement.

As an example I have created a simple ShowMessageAction. It contains a Text dependency property which you can set or databind. The Execute() method will show this Text in a MessageDialog.

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

namespace BehaviorsDemo.Behaviors {
    
    public class ShowMessageAction : DependencyObject, IAction {

        #region Text Dependency Property

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

        /// <summary> 
        /// Identifies the Text dependency property. This enables animation, styling, 
        /// binding, etc...
        /// </summary> 
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text",
                                        typeof(string),
                                        typeof(ShowMessageAction),
                                        new PropertyMetadata(string.Empty));

        

        #endregion Text Dependency Property

        public object Execute(object sender, object parameter) {
#pragma warning disable 4014
            new MessageDialog(this.Text).ShowAsync();
#pragma warning restore
            return null;
        }
    }
}

Use ShowMessageAction

After you have compiled the application you should switch to Blend to add this behavior to a control. I my case I have created a simple page containing a TextBox and a Button. In the 'Assets' window you should select the 'Behaviors'. You should see the 'ShowMessageAction' at the bottom of the list. Drag this action and drop it on the Buttton. The Text property of the action is databound to the Text property of the TextBox.

The ShowMessageAction is placed in an EventTriggerBehavior which has its EventName set to 'Click'. You can of course select a different event if you want. You can also select a different SourceObject. This is the object whose event is handled to trigger the action. The EventTriggerBehavior replaces the EventTrigger of Silverlight.

The XAML of this page.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:BehaviorsDemo"
      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:Core="using:Microsoft.Xaml.Interactions.Core"
      xmlns:Behaviors="using:BehaviorsDemo.Behaviors"
      x:Class="BehaviorsDemo.MainPage"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          DataContext="{Binding Source={StaticResource SampleDataSource}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="85*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="textBoxDemo"
                     HorizontalAlignment="Left"
                     TextWrapping="Wrap"
                     Text="Hello World"
                     Width="400" />
            <Button Content="Show"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top">
                <Interactivity:Interaction.Behaviors>
                    <Core:EventTriggerBehavior EventName="Click">
                        <Behaviors:ShowMessageAction Text="{Binding Text, ElementName=textBoxDemo}" />
                    </Core:EventTriggerBehavior>
                </Interactivity:Interaction.Behaviors>
            </Button>
        </StackPanel>
    </Grid>
</Page>

When you run the app and click the Button the text of the TextBox is shown in a MessageDialog. I think this is very cool. Designers can now add interactivity to the app without writing code.

Creating Behaviors

3 years ago I wrote this blog post Keyboard selection on Silverlight ListBox and ComboBox. It describes a Behavior which I used in my Silverlight apps. You could drop the Behavior on a ListBox or ComboBox. This enabled keyboard selection. So, if you pressed the key 'X', the ListBox/ComboBox would select the first row which started with the letter X. I always found this Behavior very userful. So I converted it to the new Windows 8.1 Behaviors. This allows me to use it for the Windows 8 DataControls (GridView, ListView and FlipView) without any extra programming effort.

You create a Behavior by adding a class to your project which derives from the DependencyObject class and implements the IBehavior interface. In Silverlight there was a Behavior and Behavior<T> base class, but that no longer exists. To make my conversion easier I re-created the Behavior<T> base class. It is an abstract class with an AssociatedObject (oftype T) property and two virtual methods OnAttached() and OnDetatching().

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

namespace BehaviorsDemo.Behaviors {

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

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

        protected virtual void OnAttached() {
        }

        protected virtual void OnDetaching() {
        }

        public void Attach(Windows.UI.Xaml.DependencyObject associatedObject) {
            this.AssociatedObject = (T)associatedObject;
            OnAttached();
        }

        public void Detach() {
            OnDetaching();
        }

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

The KeyboardSelectionBehavior class is almost a one-on-one port of my Silverlight code. I just changed some namespaces and cleaned up some code. The ListBoxItem is now replaced by a SelectorItem.

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;

namespace BehaviorsDemo.Behaviors {

    /// <summary>
    /// This behavior can be attached to a ListBox or ComboBox to 
    /// add keyboard selection
    /// </summary>
    public class KeyboardSelectionBehavior : Behavior<Selector> {

        private bool _boundToComboBoxItemsPanelRoot;

        /// <summary>
        /// Gets or sets the Path used to select the text
        /// </summary>
        public string SelectionMemberPath { get; set; }

        /// <summary>
        /// Attaches to the specified object: subscribe on KeyDown event
        /// </summary>
        protected override void OnAttached() {
            base.OnAttached();
            this.AssociatedObject.KeyDown += DoKeyDown;

            // subscribe on KeyDown event of the ItemsPanelRoot
            // of a ComboBox when it is opened
            var cb = this.AssociatedObject as ComboBox;
            if (cb != null) {
                cb.DropDownOpened += ComboBox_DropDownOpened;
            }
        }

        /// <summary>
        /// subscribe on KeyDown event of ItemsPanelRoot of a ComboBox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComboBox_DropDownOpened(object sender, object e) {
            var itemsPanelRoot = this.AssociatedObject.ItemsPanelRoot;
            if (_boundToComboBoxItemsPanelRoot == false && itemsPanelRoot != null) {
                _boundToComboBoxItemsPanelRoot = true;
                itemsPanelRoot.KeyDown += DoKeyDown;
            }
        }

        /// <summary>
        /// Detaches to the specified object: Unsubscribe on KeyDown event(s)
        /// </summary>
        protected override void OnDetaching() {
            this.AssociatedObject.KeyDown -= DoKeyDown;
            
            var cb = this.AssociatedObject as ComboBox;
            if (cb != null) {
                cb.DropDownOpened -= ComboBox_DropDownOpened;
                if (_boundToComboBoxItemsPanelRoot) {
                    var itemsPanelRoot = this.AssociatedObject.ItemsPanelRoot;
                    if (itemsPanelRoot != null) {
                        _boundToComboBoxItemsPanelRoot = false;
                        itemsPanelRoot.KeyDown -= DoKeyDown;
                    }
                }
            }
            base.OnDetaching();
        }

        /// <summary>
        /// Select the correct item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void DoKeyDown(object sender, KeyRoutedEventArgs e) {
            // Create a list of strings and indexes
            int index = 0;
            IEnumerable<Item> list = null;

            var path = SelectionMemberPath ??
                this.AssociatedObject.DisplayMemberPath;

            var evaluator = new BindingEvaluator();
            if (path != null) {
                list = this.AssociatedObject.Items.OfType<object>()
                    .Select(item => {
                        // retrieve the value using the supplied Path
                        var binding = new Binding();
                        binding.Path = new PropertyPath(path);
                        binding.Source = item;

                        BindingOperations.SetBinding(evaluator,
                            BindingEvaluator.TargetProperty, binding);
                        var value = evaluator.Target;

                        return new Item {
                            Index = index++,
                            Text = Convert.ToString(value)
                        };
                    });
            } else {
                list = this.AssociatedObject.Items.OfType<SelectorItem>()
                    .Select(item => new Item {
                        Index = index++,
                        Text = Convert.ToString(item.Content)
                    });
            }
            // Sort the list starting at next selectedIndex to the end and 
            // then from the beginning
            list = list.OrderBy(item => item.Index <=
                this.AssociatedObject.SelectedIndex ?
                item.Index + this.AssociatedObject.Items.Count : item.Index);

            // Find first starting with 
            var text = e.Key.ToString();
            var first = list.FirstOrDefault(item => item.Text.StartsWith(text,
                StringComparison.CurrentCultureIgnoreCase));

            if (first != null) {
                // found
                this.AssociatedObject.SelectedIndex = first.Index;
            }
        }

        /// <summary>
        /// Helper class
        /// </summary>
        private class Item {
            public int Index { get; set; }
            public string Text { get; set; }
        }

        /// <summary>
        /// Helper class used for property path value retrieval
        /// </summary>
        private class BindingEvaluator : FrameworkElement {

            public static readonly DependencyProperty TargetProperty =
                DependencyProperty.Register(
                    "Target",
                    typeof(object),
                    typeof(BindingEvaluator), null);

            public object Target {
                get { return GetValue(TargetProperty); }
                set { SetValue(TargetProperty, value); }
            }

        }
    }
}

Use KeyboardSelectionBehavior

To demonstrate the KeyboardSelectionBehavior I added some sample data containing Employees to the Project. An Employee has a Name, Salary and Image property. I created a GridView in which the ItemSource is data bound to the Employees sample data.

Next, I dragged the KeyboardSelectionBehavior from the Assets Window and dropped it on the GridView.

The behavior has a SelectionMemberPath property to specify which property to use for selection. In this example it is set to the Name of the databound Employee.

The XAML of this page.

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:BehaviorsDemo"
      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:Core="using:Microsoft.Xaml.Interactions.Core"
      xmlns:Behaviors="using:BehaviorsDemo.Behaviors"
      x:Class="BehaviorsDemo.MainPage"
      mc:Ignorable="d">
    <Page.Resources>
        <DataTemplate x:Key="EmployeesItemTemplate">
            <Grid Height="110"
                  Width="480"
                  Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}"
                        Width="110"
                        Height="110">
                    <Image Source="{Binding Image}"
                           Height="110"
                           Width="110" />
                </Border>
                <StackPanel Grid.Column="1"
                            Margin="10,0,0,0">
                    <TextBlock Text="{Binding Name}"
                               Style="{StaticResource TitleTextBlockStyle}" />
                    <TextBlock Text="{Binding Salary}"
                               Style="{StaticResource CaptionTextBlockStyle}"
                               TextWrapping="NoWrap" />
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          DataContext="{Binding Source={StaticResource SampleDataSource}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="85*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
            <TextBox x:Name="textBoxDemo"
                     HorizontalAlignment="Left"
                     TextWrapping="Wrap"
                     Text="Hello World"
                     Width="400" />
            <Button Content="Show"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top">
                <Interactivity:Interaction.Behaviors>
                    <Core:EventTriggerBehavior EventName="Click">
                        <Behaviors:ShowMessageAction Text="{Binding Text, ElementName=textBoxDemo}" />
                    </Core:EventTriggerBehavior>
                </Interactivity:Interaction.Behaviors>
            </Button>

        </StackPanel>
        <GridView ItemTemplate="{StaticResource EmployeesItemTemplate}"
                  ItemsSource="{Binding Employees}"
                  IsSwipeEnabled="False"
                  Grid.Row="1"
                  SelectionMode="Single">
            <Interactivity:Interaction.Behaviors>
                <Behaviors:KeyboardSelectionBehavior SelectionMemberPath="Name" />
            </Interactivity:Interaction.Behaviors>
        </GridView>

    </Grid>
</Page>

Run the app and focus the GridView. You can then press the key 'C' when and it will select 'Carpenter, Terry' employee.

Closure and download

I hope you like my solution. Maybe you can use it in your apps too. You can download my code below. My colleague MVP Timmy Kokke has created the http://www.blendbehaviors.net/ website which is used to share Behaviors and Actions. You will soon be able to download my ShowMessageAction and KeyBoardSelectionBehavior there as well.

Cheers,

Fons

Keyboard selection on Silverlight ListBox and ComboBox

3 Comments
By Fons Sonnemans, 7-2-2010

Silverlight doesn't support keyboard selection on a ListBox or Combox. I have created a small Behavior which fixes this problem. You can attach the KeyboardSelectionBehavior to a ListBox or ComboBox using Microsoft Expression Blend. You drag it from the Assets and drop it on your ComboBox or ListBox. If you have a custom ItemTemplate you will have to set the SelectionMemberPath property.

Try my behavior below. If you press a key on the ComboBox or ListBoxes it will select the next item starting with the given key.

Get Microsoft Silverlight

The ComboBox in this example is not databound, The behavior uses the Convert.ToString() method to convert the Content of each ListBoxItem/ComboBoxItem to a string. An invariant case insensitive StartWith() comparison is used to find the next item.

The left ListBox is databound to SampleData containing Employees. The behavior uses the DisplayMemberPath of the ListBox. The Name of the databound Employee is used for keyboard selection.

<ListBox Margin="79,64,0,23" DisplayMemberPath="Name"

    ItemsSource="{Binding Employees}" HorizontalAlignment="Left" Width="176">

    <i:Interaction.Behaviors>

        <local:KeyboardSelectionBehavior />

    </i:Interaction.Behaviors>

</ListBox>

The right ListBox is also databound but has a custom ItemTemplate. We can't use the DisplayMemberPath. The behavior has a SelectionMemberPath property to specify which property to use for selection. In this example it is set to the Name of the databound Employee.

<ListBox Margin="272,23,23,23"

    ItemsSource="{Binding Employees}">

    <i:Interaction.Behaviors>

        <local:KeyboardSelectionBehavior SelectionMemberPath="Name"/>

    </i:Interaction.Behaviors>

    <ListBox.ItemTemplate>

        <DataTemplate>

            <StackPanel Orientation="Horizontal">

                <Image Source="{Binding Image}" Height="32" Width="32"/>

                <StackPanel>

                    <TextBlock Text="{Binding Name}" FontWeight="Bold"/>

                    <TextBlock Text="{Binding Description}"/>

                </StackPanel>

            </StackPanel>

        </DataTemplate>

    </ListBox.ItemTemplate>

</ListBox>

Here is the code:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Windows.Data;

using System.Windows.Input;

using System.Windows.Interactivity;

 

namespace ReflectionIT.Silverlight.Behaviors {

 

    ///<summary>

    /// This behavior can be attached to a ListBox or ComboBox to

    /// add keyboard selection

    ///</summary>

    public class KeyboardSelectionBehavior : Behavior<Selector> {

 

        ///<summary>

        /// Gets or sets the Path used to select the text

        ///</summary>

        public string SelectionMemberPath { get; set; }

 

        public KeyboardSelectionBehavior() {}

 

        ///<summary>

        /// Attaches to the specified object: subscribe on KeyDown event

        ///</summary>

        protected override void OnAttached() {

            base.OnAttached();

            this.AssociatedObject.KeyDown += DoKeyDown;

        }

 

        void DoKeyDown(object sender, KeyEventArgs e) {

 

            // Create a list of strings and indexes

            int index = 0;

            IEnumerable<Item> list = null;

            var path = SelectionMemberPath ??

                this.AssociatedObject.DisplayMemberPath;

            var evaluator = new BindingEvaluator();

            if (path != null) {

                list = this.AssociatedObject.Items.OfType<object>()

                    .Select(item => {

                        // retrieve the value using the supplied Path

                        var binding = new Binding();

                        binding.Path = new PropertyPath(path);

                        binding.Source = item;

 

                        BindingOperations.SetBinding(evaluator,

                            BindingEvaluator.TargetProperty, binding);

                        var value = evaluator.Target;

 

                        return new Item { Index = index++,

                                Text = Convert.ToString(value) };

                    });

            } else {

                list = this.AssociatedObject.Items.OfType<ListBoxItem>()

                    .Select(item => new Item { Index = index++,

                                Text = Convert.ToString(item.Content) });

            }

            // Sort the list starting at next selectedIndex to the end and

            // then from the beginning

            list = list.OrderBy(item => item.Index <=

                this.AssociatedObject.SelectedIndex ?

                item.Index + this.AssociatedObject.Items.Count : item.Index);

 

            // Find first starting with

            var text = e.Key.ToString();

            var first = list.FirstOrDefault(item => item.Text.StartsWith(text,

                StringComparison.InvariantCultureIgnoreCase));

            if (first != null) {

                // found

                this.AssociatedObject.SelectedIndex = first.Index;

            }

        }

 

        ///<summary>

        /// Helper class

        ///</summary>

        private class Item {

            public int Index;

            public string Text;

        }

 

        ///<summary>

        /// Helper class used for property path value retrieval

        ///</summary>

        private class BindingEvaluator : FrameworkElement {

 

            public static readonly DependencyProperty TargetProperty =

                DependencyProperty.Register(

                    "Target",

                    typeof(object),

                    typeof(BindingEvaluator), null);

 

            public object Target {

                get { return GetValue(TargetProperty); }

                set { SetValue(TargetProperty, value); }

            }

        }

 

    }

}

Updated 1-feb-2011

Sudeept Malik send me an email with some code changes which now makes it possible to use the keyboard in an open ComboBox.

You can download the code including the sample application here.

Silverlight Behaviors and Commands

0 Comments
By Fons Sonnemans, 21-12-2009

A few months ago I wrote an blog post about a Silverlight 3.0 LetItSnowBehavior. This Behavior can be used to add a Snow effect to a Canvas. Very usefull if you want to create a christmas card.

This behavior was always showing you falling snow flakes. You couldn't stop and (re)start the gameloop. The best way to implement this is by adding Commands to the behavior. This allows you to select one or more triggers to Start or Stop the gameloop. I have added the Start and Stop properties of the type ICommand to the LetItSnowBehavior class. In the constructor I have initialized these properties with new ActionCommand objects (Microsoft.Expression.Interactions.dll) and delegates to the OnStop() and OnStart() methods. Triggers attached to these commands will execute the these methods.

public class LetItSnowBehavior : Behavior<Canvas> {

 

    private DispatcherTimer _gameLoop = new DispatcherTimer();

 

    public LetItSnowBehavior() {

        // Create Commands

        this.Start = new ActionCommand(this.OnStart);

        this.Stop = new ActionCommand(this.OnStop);

 

        // Init timer

        _gameLoop.Interval = new TimeSpan(0, 0, 0, 0, 10);

        _gameLoop.Tick += new EventHandler(gameLoop_Tick);

        _gameLoop.Start();

    }

 

    public ICommand Start { get; private set; }

 

    public ICommand Stop { get; private set; }

 

    private void OnStart() {

        _gameLoop.Start();

    }

 

    private void OnStop() {

        _gameLoop.Stop();

    }

 

    ...

You can use Expression Blend to attach triggers to the Commands. Select the LetItSnowBehavior object and open it properties. Click the + button for the Start property and select an EventTrigger. Select the 'buttonStart' as source and the 'Click' as event. You can even add multiple triggers to the command.

Set Triggers on LetItSnowBehavior in Expression Blend 3.0

You can download the code from here.

Control a MediaElement using a custom Behavior

0 Comments
By Fons Sonnemans, 11-10-2009

Controlling a MediaElement in Silverlight isn't difficult. You use the Play(), Stop() and Pause() methods in your code. I have written the 'ControlMediaElementAction' Behavior which makes it even easier. You don't have to write a single line of code. The ControlMediaElementAction is associated with a MediaElement. It has a ControlMediaElementOption which you can set to Play, Stop, Pause and RewindAndPlay. The Invoke() methods controls (Plays, Stops, Pauses and RewindAndPlays) the AssociatedObject (MediaElement).

public class ControlMediaElementAction : TriggerAction<MediaElement> {

 

    protected override void Invoke(object o) {

        switch (ControlMediaElementOption) {

            case ControlMediaElementOption.Play:

                this.AssociatedObject.Play();

                break;

            case ControlMediaElementOption.Stop:

                this.AssociatedObject.Stop();

                break;

            case ControlMediaElementOption.Pause:

                this.AssociatedObject.Pause();

                break;

            case ControlMediaElementOption.RewindAndPlay:

                this.AssociatedObject.Position = TimeSpan.Zero;

                this.AssociatedObject.Play();

                break;

            default:

                break;

        }

    }

 

    public ControlMediaElementOption ControlMediaElementOption { get; set; }

 

}

 

public enum ControlMediaElementOption {

    Play, Stop, Pause, RewindAndPlay

}

You assign a ControlMediaElementAction to a MediaElement. In Expression Blend you drag it from you Asset tab and drop it on a MediaElement. Then you can select your trigger and set all other properties from the Properties tab.

ControlMediaElementAction in Blend 3.0

In the following example I have 3 ControlMediaElementAction assigned to a MediaElement. The first is triggerd by the 'Click' event of 'buttonPlay' and uses the 'Play' option. The second is triggerd by the 'Click' event of 'buttonPause' and uses the 'Pause' option. The third is triggerd by the 'MediaEnded' event of the MediaElement and uses the 'RewindAndPlay' option, making the movie loop.

<UserControl

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

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

   xmlns:local="clr-namespace:SilverlightApplication7"

    x:Class="SilverlightApplication7.MainPage"

    Width="640" Height="480">

 

    <Grid x:Name="LayoutRoot" Background="White">

        <MediaElement x:Name="SL_wmv" Height="200" HorizontalAlignment="Left"

                 Margin="160,40,0,0" Width="160" Source="/SL.wmv"

                 Stretch="Fill" AutoPlay="False">

            <i:Interaction.Triggers>

                <i:EventTrigger SourceName="buttonPlay" EventName="Click">

                    <local:ControlMediaElementAction/>

                </i:EventTrigger>

                <i:EventTrigger SourceName="buttonPause" EventName="Click">

                    <local:ControlMediaElementAction

                       ControlMediaElementOption="Pause"/>

                </i:EventTrigger>

                <i:EventTrigger EventName="MediaEnded">

                    <local:ControlMediaElementAction

                       ControlMediaElementOption="RewindAndPlay"/>

                </i:EventTrigger>

            </i:Interaction.Triggers>

        </MediaElement>

        <Button x:Name="buttonPlay" Height="40" Margin="160,0,0,160"

               Width="160" Content="Play"/>

        <Button x:Name="buttonPause" Height="40" Margin="160,0,0,80"

               Width="160" Content="Pause"/>

    </Grid>

</UserControl>

You can download the sourcecode here.

Silverlight 3.0 LetItSnowBehavior

0 Comments
By Fons Sonnemans, 14-8-2009

Silverlight 3.0 has a great new feature called Behaviors. You can use them for a lot of things, one of them is to create a SNOW effect in a canvas. I know it is not yet Christmas but I like to be prepared. If you don't know what a behavior is or how to write them read first this blog post from Andrea Boschin.

You can download the sourcecode from here.

Usage

To add the LetITSnow Behavior to your own application you first have to reference the 'ReflectionIT.Behavior.dll'. Then you can apply the LetItSnowBehavior from Expression Blend 3.0 by dragging it from the Asset Tab onto an empty Canvas. That's all.

The Canvas will now have a Interaction.Behaviors element with the LetItSnowBehavior in it.

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:ritb="clr-namespace:ReflectionIT.Behaviors;assembly=ReflectionIT.Behaviors" 
    x:Class="BehaviorDemo.MainPage"
    Width="500" Height="400">
    <Grid x:Name="LayoutRoot" Background="White">
        <Canvas>
            <i:Interaction.Behaviors>
                <ritb:LetItSnowBehavior/>
            </i:Interaction.Behaviors>
        </Canvas>
        <TextBlock FontSize="50" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="LetItSnowBehavior " /> </Grid> </UserControl>

LetItSnowBehavior implementation

The LetItSnowBehavior was created using the Behavior 'Add New Item...' template from Blend 3.0. The overriden OnAttached method generates the SnowFlakes. The SnowFlake is an Image with a random size, speed and opacity. The position is updated every 10 milliseconds using a DispatcherTimer.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Threading;

namespace ReflectionIT.Behaviors {

    public class LetItSnowBehavior : Behavior<Canvas> {

        private DispatcherTimer _gameLoop = new DispatcherTimer();
        private int _numberOfFlakes = 200;

        public LetItSnowBehavior() {
            _gameLoop.Interval = new TimeSpan(0, 0, 0, 0, 10);
            _gameLoop.Tick += 
     new EventHandler(gameLoop_Tick); } protected override void OnAttached() { base.OnAttached(); this.AssociatedObject.SizeChanged +=
      new SizeChangedEventHandler (AssociatedObject_SizeChanged); } protected override void OnDetaching() { base.OnDetaching(); _gameLoop.Stop(); this.AssociatedObject.Children.Clear(); } public int NumberOfFlakes { get { return _numberOfFlakes; } set { _numberOfFlakes = value; } } private void GenerateSnowFlakes() { this.AssociatedObject.Children.Clear(); for (int i = 0; i < NumberOfFlakes; i++) { SnowFlake flake = new SnowFlake(i, this.AssociatedObject.ActualHeight,
     this.AssociatedObject.ActualWidth); this.AssociatedObject.Children.Add(flake); } } private void AssociatedObject_SizeChanged(object sender, SizeChangedEventArgs e) { GenerateSnowFlakes(); _gameLoop.Start(); } private void gameLoop_Tick(object sender, EventArgs e) { foreach (SnowFlake flake in this.AssociatedObject.Children) { flake.Update(); } } } }

You can download the sourcecode from here.

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.