vladeck's junkyard

designer wannabe

Animated ErrorInfo Behavior

on September 24, 2010

Ever since I wrote Animated Height Behavior, I wanted to create Behavior that will give me nice eye-candy for all my IDataErrorInfo validation needs. You can see the result in the video below, as well as the code of the Behavior. Please note that the image I’m using here is from the Resources (just add 16×16 png image to Resources and name it Error16). GetBitmapSource() is Extension that I wrote for converting System.Drawing.Bitmap to BitmapSource.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Foo
{
    public class AnimatedErrorInfoBehavior : Behavior<FrameworkElement>
    {
        private readonly TextBlock textBlock_;
        private Grid errorGrid_;

        private readonly DoubleAnimation increaseHeightAnimation_;
        private readonly Storyboard increaseHeightStoryboard_;

        private readonly DoubleAnimation showAnimation_;
        private readonly Storyboard showStoryboard_;

        private readonly DoubleAnimation decreaseHeightAnimation_;
        private readonly Storyboard descreaseHeightStoryboard_;

        private readonly DoubleAnimation hideAnimation_;
        private readonly Storyboard hideStoryboard_;

        private readonly Dictionary<string, int> errors_;

        public AnimatedErrorInfoBehavior()
        {
            errors_ = new Dictionary<string, int>();
            textBlock_ = new TextBlock();

            increaseHeightAnimation_ = new DoubleAnimation(
                0.0,
                23.0,
                TimeSpan.FromMilliseconds(200)
            );

            increaseHeightAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
            increaseHeightStoryboard_ = new Storyboard();

            Storyboard.SetTarget(increaseHeightAnimation_, errorGrid_);
            Storyboard.SetTargetProperty(increaseHeightAnimation_, new PropertyPath(FrameworkElement.HeightProperty));
            increaseHeightStoryboard_.Children.Add(increaseHeightAnimation_);

            showAnimation_ = new DoubleAnimation(0.0, 1.0, TimeSpan.FromMilliseconds(200));
            showAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseIn };
            showStoryboard_ = new Storyboard();

            Storyboard.SetTarget(showAnimation_, textBlock_);
            Storyboard.SetTargetProperty(showAnimation_, new PropertyPath(UIElement.OpacityProperty));
            showStoryboard_.Children.Add(showAnimation_);

            decreaseHeightAnimation_ = new DoubleAnimation(
                23.0,
                0.0,
                TimeSpan.FromMilliseconds(100)
            );

            descreaseHeightStoryboard_ = new Storyboard();

            Storyboard.SetTarget(decreaseHeightAnimation_, errorGrid_);
            Storyboard.SetTargetProperty(decreaseHeightAnimation_, new PropertyPath(FrameworkElement.HeightProperty));
            descreaseHeightStoryboard_.Children.Add(decreaseHeightAnimation_);

            hideAnimation_ = new DoubleAnimation(1.0, 0.0, TimeSpan.FromMilliseconds(100));
            hideAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };

            hideStoryboard_ = new Storyboard();

            Storyboard.SetTarget(hideAnimation_, textBlock_);
            Storyboard.SetTargetProperty(hideAnimation_, new PropertyPath(UIElement.OpacityProperty));
            hideStoryboard_.Children.Add(hideAnimation_);
        }

        protected override void OnAttached()
        {
            var parent = VisualTreeHelper.GetParent(AssociatedObject) as Grid;

            if (parent == null)
                return;

            var row_index = Grid.GetRow(AssociatedObject);
            var row_span = Grid.GetRowSpan(AssociatedObject);

            var column_index = Grid.GetColumn(AssociatedObject);
            var column_span = Grid.GetColumnSpan(AssociatedObject);

            parent.Children.Remove(AssociatedObject);

            var grid = new Grid();
            grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
            grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

            Grid.SetRow(grid, row_index);
            Grid.SetRowSpan(grid, row_span);

            Grid.SetColumn(grid, column_index);
            Grid.SetColumnSpan(grid, column_span);

            grid.Children.Add(AssociatedObject);
            Grid.SetRow(AssociatedObject, 0);

            errorGrid_ = new Grid();
            errorGrid_.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            errorGrid_.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });

            errorGrid_.Height = 0;

            var image = new Image();
            image.Source = Resources.Error16.GetBitmapSource();
            image.Margin = new Thickness(0,0,2,7);

            errorGrid_.Children.Add(image);
            Grid.SetColumn(image, 0);

            textBlock_.Foreground = Brushes.Red;

            errorGrid_.Children.Add(textBlock_);
            Grid.SetColumn(textBlock_, 1);

            grid.Children.Add(errorGrid_);
            Grid.SetRow(errorGrid_, 1);

            parent.Children.Add(grid);

            Validation.AddErrorHandler(AssociatedObject, OnError);
            Validation.SetErrorTemplate(AssociatedObject, null);

            base.OnAttached();
        }

        private void OnError(object sender, ValidationErrorEventArgs e)
        {
            var error_msg = e.Error.ErrorContent.ToString();

            if (e.Action == ValidationErrorEventAction.Added)
            {
                if (errors_.ContainsKey(error_msg))
                    errors_[error_msg]++;
                else
                    errors_.Add(error_msg, 1);

                if (errors_[error_msg] == 1)
                {
                    textBlock_.Text = e.Error.ErrorContent.ToString();

                    increaseHeightStoryboard_.Begin(errorGrid_);
                    showStoryboard_.Begin(textBlock_);
                }
            }

            if (e.Action == ValidationErrorEventAction.Removed)
            {
                errors_[error_msg]--;

                if (errors_[error_msg] == 0)
                {
                    descreaseHeightStoryboard_.Begin(errorGrid_);
                    hideStoryboard_.Begin(textBlock_);
                }
            }
        }
    }
}

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.