Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Animated Auto-Correct

4.85/5 (11 votes)
27 Dec 2014CPOL9 min read 23.6K  
Auto-correct ruining our spelling, the solution is to animate the changes

Animated Auto-correct

Problem

Autocorrect ruining our spelling

Solution

Animate the changes

Introduction

English is not my mother tongue, so I am struggling with the spelling every time I try to match the wrong word that I just wrote with the right one, character by character, trying to find what's wrong.

By animating the changes, it will be just a matter of seeing the animation and will know what character has been added and what character has been removed. By just watching the animation running you will learn your mistakes.

Imagine any text insertion control in any device running this animation, you will never miss any spelling mistake any more.

I hope one of office team reads my tip and adds this feature in Word, I will be glad if that happens. :)

Before we can see how I make it

Let's make a demo

In this demo I will give you all the technical knowledge that you need to make this project by your own, it's so simple so let's do a proof of concept that make one character moves up and down and lets play with it a little bit.  

1. First let's create our TextBlock

<TextBlock x:Name="MyTextBlock" Text="Some Text" Margin="20" FontSize="25"></TextBlock>

2. then we need to set a new object of the type TextEffectCollection in MyTextBlock.TextEffects, this collection will contain TextEffect objects for each character so we can manipulate each character alone.  

MyTextBlock.TextEffects = new TextEffectCollection();

Then we need to loop throw the character and set TextEffect object for each one of them, but wait, how you are going to construct this object and how to use it, first this class (TextEffect) has property called Transform we will add a new TransformGroup on it, but this TransformGroup should contain all the Transformation object that we are planning to apply on each char for me here I have TranslateTransform and ScaleTransform we will need those object later to use it in the animation.

 TranslateTransform used to shift the characters from their original place to somewhere else, we will use two properties to do that X and Y.

 ScaleTransform used to change the size of our characters 1 for it's normal size and you can stretch it or shrinking it with the properties ScaleX and ScaleY.

for (var i = 0; i < MyTextBlock.Text.Count(); i++)
{
    var transGrp = new TransformGroup();
    transGrp.Children.Add(new TranslateTransform());
    transGrp.Children.Add(new ScaleTransform());
    MyTextBlock.TextEffects.Add(new TextEffect
    {
        PositionStart = i,
        PositionCount = 1,
        Transform = transGrp,
    });
}

Now we construct the TextBlock Control, we can start paying with each char and animate it.

We will start with Storyboard, we are using this class to add all our animation into it just like an actions in real story and it will take care of playing it for us, for example you can say move this rectangle to there then wait 3 sec then make this circle bigger 2 times, when you call Begin function in the story board it will start playing your animations.

var storyboard = new Storyboard();

Then let's create our animation, and we should not forget to specify the target control for our animation to work on.

var animation = new DoubleAnimation<span dir="RTL">();</span>

animation.SetValue(Storyboard.TargetNameProperty, MyTextBlock.Name);

Then let's do something with our animation, we created before TranslateTransform and ScaleTransform we will use them now to shift our character 40 point from its original place we will use the property To for that, and it will take 1sec to do so, we will use the property Duration to do that, also the animation should start immediately we will use the property BeginTime and finally it should not reverse, if we set it to True the character will move from its original place and come back to it, so the animation will take twice the time, lets specify which property in the targeted control will be affected with the animation we just did.   

animation.To = 40;
animation.Duration = TimeSpan.FromSeconds(1);
animation.BeginTime = TimeSpan.FromSeconds(1);
animation.AutoReverse = false;
var charIndex = 0;
Storyboard.SetTargetProperty(animation, new PropertyPath(
String.Format("TextEffects[{0}].Transform.Children[0].Y", charIndex)));

Finally, let's add this animation to the storyboard and run it.

storyboard.Children.Add(animation);
storyboard.Begin(this);

By doing this we will start see our character move down and stay there, and that’s because the (0, 0) is in the top left corner and we told the animation to move it to (0, 40) and that's down in the Y axis.

Now let's change something and see the effect of it on our character, what if we change the Scale to 2, but only after the moves end.

Creating the animation

var doubleAnimation2 = new DoubleAnimation();
doubleAnimation2.SetValue(Storyboard.TargetNameProperty, MyTextBlock.Name);

changing the properties to fit our needs, we need the scale happened after the move done so we have to set begin time to 2 sec one for the move animation to start and one the duration of the first animation then this will begin,

var animation2 = doubleAnimation2;
animation2.To = 2;
animation2.Duration = TimeSpan.FromSeconds(1);
animation2.BeginTime = TimeSpan.FromSeconds(2);
animation2.AutoReverse = false;
Storyboard.SetTargetProperty(animation2, new PropertyPath(
String.Format("TextEffects[{0}].Transform.Children[1].ScaleX", charIndex)));

Finally, add it to the story board and watch the magic begins.

storyboard.Children.Add(animation);
storyboard.Begin(this);

But if we want to play with another character we just need to change the charIndex variable and the animation will be applied to the character in that index.

Let's try to change the character color, first we will start with the animation, and like always setting the target control, but this time we will not put the animation inside the storyboard but there is a separate property in the TextEffect to put our color animation inside, so we need to get the right TextEffect from the right index and then out the color animation inside.

var colorAnimation = new ColorAnimation
{
    To = Colors.Green,
    Duration = TimeSpan.FromSeconds(0),
};
colorAnimation.SetValue(Storyboard.TargetNameProperty, MyTextBlock.Name);
var solidColorBrush = new SolidColorBrush();
solidColorBrush.BeginAnimation(SolidColorBrush.ColorProperty,colorAnimation);
MyTextBlock.TextEffects[charIndex].Foreground = solidColorBrush;

And Voallah the character color changed.  

We understand how to move the characters, how to play with its scale and how change its color.

Correct it by Animation (storyboards)

We need to unify a way, to describe the changes that happened in the word in other word, if we have this wrong spelled word "requir" how we are going to correct it, insert e at the end right! , this is what should we do by the animation, but what are the other spelling mistakes we can make, rather that forgetting a character, we might add non necessary one, we might swap two chars or we might use a character instead of another. From this four categories of mistakes we created our four animation methods that correct them using animation.

Every animation method contains steps (DoubleAnimation) effecting specific character, so if we need to do it on more than one character we need to call it more than one time but with different parameters.   

Insert

Before we start with the method we need to see the helper methods GetDoubleAnimation, MoveVertically, Scale, MoveHorizontally, SetColor.

This will just create an instance of our animation and specify the target control.

public DoubleAnimation GetDoubleAnimation()
        {
            var doubleAnimation = new DoubleAnimation();
            doubleAnimation.SetValue(Storyboard.TargetNameProperty,mohamedAhmed.Name);
            return doubleAnimation;
        }

All the helper methods just to hide the creation of the animation and the internal details of the animation parameters, this will make the stories more readable.

public DoubleAnimation MoveVertically(double to, Duration duration, TimeSpan? timeSpan, int index, bool autoReverse =false)
        {
            var animation = GetDoubleAnimation();
            animation.To = to;
            animation.Duration = duration;
            if (timeSpan != null)
                animation.BeginTime = timeSpan;
            animation.AutoReverse = autoReverse;
            Storyboard.SetTargetProperty(animation, new PropertyPath(
            String.Format("TextEffects[{0}].Transform.Children[0].Y", index)));
            return animation;
        }

Every story function create the animations with the right order to describe the error correction, in other words insert function insert new character when the user forget it, the new character comes from nowhere above the word and move down till he hit the middle. First move the character away in top, make it disappear by scaling it to 0, and this two steps should happen in no time, then the color should be green, and the movement should start till it hit the original place 0.

private TimeSpan Insert(int index, Storyboard storyboard,TimeSpan timeLine,TimeSpan duration)
        {
            timeLine += duration;
            storyboard.Children.Add(MoveVertically(-40, _noTime, null, index));
            storyboard.Children.Add(Scale(0, _noTime, null, index));
            SetColor(index);
            storyboard.Children.Add(Scale(1, _noTime, timeLine, index));
            storyboard.Children.Add(MoveVertically(0, duration, timeLine, index));
            return timeLine;
        }

Remove

You will find all the story functions takes and return the timeline, every story corrects one character, when you have more than one char to correct you should know when to run your animations, to get the final effect of, the first character get corrected and then the second character get corrected, if we don't have this timeLine all the characters will be corrected in the sometime and the user will get confused, he will say "what just happened!!??"

private TimeSpan Remove(int index, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)
        {
            SetColor(index,false);
            storyboard.Children.Add(MoveVertically(40,duration,timeLine,index));
            timeLine += duration;
            storyboard.Children.Add(Scale(0,_noTime,timeLine,index));
            return timeLine;
        }

Swap

There is area of improvement here, we get the hole string width by using the " FormattedText" and we assumed our character width is the average and this is not the case, the char width could vary, this is why the swap is a little sloppy. So if you found a better way to measure the width of the character it will be more accurate.  

private TimeSpan Swap(int index1, int index2, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)
        {
            timeLine += duration;

            storyboard.Children.Add(MoveVertically(-AverageCharWidth(),duration,timeLine,index1,true));
            storyboard.Children.Add(MoveVertically(-AverageCharWidth(),duration,timeLine,index2,true));
            var distance = Math.Abs(index1 - index2);
            var to = AverageCharWidth() * distance;
            storyboard.Children.Add(MoveHorizontally(to, duration, timeLine, index1));
            storyboard.Children.Add(MoveHorizontally(-to, duration, timeLine, index2));

            return timeLine;

        }

Replace

private TimeSpan Replace(int newIndex, int oldIndex, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)
        {
            SetColor(newIndex);
            SetColor(oldIndex,false);

            storyboard.Children.Add(MoveVertically(-AverageCharWidth(), _noTime, _noTime, newIndex));
            storyboard.Children.Add(Scale(0, _noTime, _noTime, newIndex));
            storyboard.Children.Add(MoveHorizontally(-AverageCharWidth(), _noTime, _noTime, newIndex));

            timeLine += duration;

            storyboard.Children.Add(Scale(1, _noTime, timeLine, newIndex));
            storyboard.Children.Add(MoveVertically(0, duration, timeLine, newIndex));
            storyboard.Children.Add(MoveVertically(AverageCharWidth(), duration, timeLine, oldIndex));
            return timeLine;
        }

   

How I Did It

Steps

  1. When the user chooses a word from the autocorrect pop up menu, the wrong word and the right word will be known.

    popup

  2. Then we feed these two words (wrong word and the right word) to the "Find changes algorithm" to find the changes, it will return list of changes (list of steps) if we animate this changes in the wrong word, we will end up having the right one.
    C#
    public enum ChangeType
    {
           Insert = 1,
           Remove = 2,
           Swap = 3,
           Replace = 4,
    }
  3. We then use this changes steps to animate the wrong word to be spelled correctly.

    Image 3

UI

There is an object in TextBlock called TextEffects that we use to apply the animation in the characters.

Any changes come from the algorithm, translated to DoubleAnimation then feed to the TextEffect, by doing that each character takes its animation and play it.

C#
public DoubleAnimation MoveVertically
(double to, Duration duration, TimeSpan? timeSpan, int index, bool autoReverse =false)
public DoubleAnimation MoveHorizontally
(double to, Duration duration, TimeSpan? timeSpan, int index, bool autoReverse = false)
public DoubleAnimation Scale
(double to, Duration duration, TimeSpan? beginTime, int index, bool autoReverse = false)
public void SetColor(int index,bool isGreen=true)

Those are the core animation functions, then come the functions that convert the ChangeTypes to animation.

C#
private TimeSpan Remove
(int index, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)
private TimeSpan Insert
(int index, Storyboard storyboard,TimeSpan timeLine,TimeSpan duration)
private TimeSpan Replace
(int newIndex, int oldIndex, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)
private TimeSpan Swap
(int index1, int index2, Storyboard storyboard, TimeSpan timeLine, TimeSpan duration)

As you can see, any function returns TimeSpan, we are using this time span to get the total timeline period.

C#
public async Task AnimateIt(List<Change> changes,Action done)
        {
            PrepareTextEffect(changes);

            var storyboard = new Storyboard();
            var timeLine = TimeSpan.FromSeconds(0);
            var duration = TimeSpan.FromSeconds(0.5);

            foreach (var change in changes)
            {
                switch (change.ChangeType)
                {
                    case ChangeType.Insert:
                    {
                        timeLine += Insert
                        (_wrongWordStarts + change.Index, storyboard, timeLine, duration);
                    }
                        break;
                    case ChangeType.Remove:
                    {
                        timeLine += Remove
                        (_wrongWordStarts + change.Index, storyboard, timeLine, duration);
                    }
                    break;
                    case ChangeType.Swap:
                    {
                        timeLine += Swap
                        (_wrongWordStarts + change.Index, _wrongWordStarts + change.Index2.Value, 
                        storyboard, timeLine, duration);
                    }
                    break;
                    case ChangeType.Replace:
                    {
                        var newIndex = _wrongWordStarts + change.Index + 1;
                        var oldIndex = _wrongWordStarts + change.Index;
                        timeLine += Replace
                        (newIndex, oldIndex, storyboard, timeLine, duration);
                    }
                    break;
                }
                timeLine += duration;
            }

            storyboard.Begin(this);
            await Task.Delay( duration );
            done();
        }

Any one of the functions Insert, Remove, Replace, Swap add its own DoubleAnimation to the StoryBoard, when you run the StoryBoard each animation play, the sum of all these animations is the action, either it's insert or remove or…

Animation

There is a small problem. We said before, we are using the TextEffect to run our animations, the problem is TextBox does not have this property, it's just on the TextBlock. So we had to let the user write the text in TextBox but when he corrects any word, we hide the TextBox and show TextBlock to run the animation then hide it again, it's a sloppy thing to do but for the sake of simplicity we did it.

Detecting the Change
public List<Change> Find(string wrong, string right)
       {
           var submatches = SubMatches(wrong, right);

           var insert = GetInsertions(right, submatches);
           var remove = GetRemoves(wrong, submatches);
           var swaps = GetSwaps(wrong, right, submatches);

           var all = insert.Union(remove).Union(swaps).Union(replace).ToList();
           return all;
       }

First, we find any matches between the wrong word and the right one.

Matches

Then we find the new characters that we need to add to the wrong word to be like the right one, we can find that in the insert object.

Any character that does not exist in the right and exists in the wrong, it should be removed, and we can find that in the remove object.

If there is more than one swap, the user will be confused so we are checking if there is one swap or not.

The sum of all these changes converts the wrong word to be like the right one.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)