Monday, October 02, 2006

<Blink> on steroids...

One of the long-standing jokes at the office is about using the much hated <Blink> tag in XAML user interfaces. (The other one being about mapping the UI onto the inside of a sphere..)

Disclaimer: This is NOT in ANY WAY suggested for any use WHATSOEVER. We have already seen way too many blinking webpages, thank you very much. And the code is "proof-of-concept" quality..

So basically, I wanted to do a a component that I can use in XAML, making it's content blink. And just for the fun of it, I wanted the on and off durations tunable, and the same goes for the durations of the fade in and fade out between those two extremes.

So here goes nothing...

First a little test case:

<Window x:Class="Blink.Window1"
xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src
="clr-namespace:BlinkTest" Title="Blink" Height="300" Width="450">

<StackPanel Orientation="Vertical">
<!-- Use default blinking settings -->
<src:Blink>
<TextBlock Padding="20" Margin="20" Background="Pink">Hello World</TextBlock>
</src:Blink>
<!-- Explicitely specify blinking settings -->
<src:Blink OnDuration="1" OffDuration="1" FadeInDuration="0" FadeOutDuration="0">
<StackPanel Orientation="Horizontal" Margin="50">
<Label>Some nice text about the button</Label>
<Button>Click Me!</Button>
</StackPanel>
</src:Blink>
</StackPanel>
</Window>


And then the code for the Blink class


The using statements


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

We are inheriting System.Windows.Controls.Control so we have to do some cruft related to layout




protected override int VisualChildrenCount {
get { return Child != null ? 1 : 0; }
}

protected override Visual GetVisualChild(int index) {
if (index > 0 || Child == null) throw new ArgumentException("index");
return Child;
}

protected override Size MeasureOverride(Size constraint) {
Size sizeDesired
= new Size(0, 0);
if (Child != null) {
Child.Measure(constraint);
}
sizeDesired.Width
+= Child.DesiredSize.Width;
sizeDesired.Height
+= Child.DesiredSize.Height;

return sizeDesired;
}

protected override Size ArrangeOverride(Size arrangeBounds) {
if (Child != null) {
Rect rect
= new Rect(
new Point((arrangeBounds.Width - Child.DesiredSize.Width) / 2,
(arrangeBounds.Height
- Child.DesiredSize.Height) / 2),
Child.DesiredSize);
Child.Arrange(rect);
}
return arrangeBounds;
}


And for the animation to work we need a number of dependency properties for the durations




Now, all that is left is just the blinking animation. An obvious choice for

// Using a DependencyProperty as the backing store for OffDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OffDurationProperty;

// Using a DependencyProperty as the backing store for OnDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OnDurationProperty;

// Using a DependencyProperty as the backing store for RiseDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FadeInDurationProperty;

// Using a DependencyProperty as the backing store for DeclineDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FadeOutDurationProperty;

public double OnDuration {
get { return (double)GetValue(OnDurationProperty); }
set { SetValue(OnDurationProperty, value); }
}

public double OffDuration {
get { return (double)GetValue(OffDurationProperty); }
set { SetValue(OffDurationProperty, value); }
}

public double FadeInDuration {
get { return (double)GetValue(FadeInDurationProperty); }
set { SetValue(FadeInDurationProperty, value); }
}

public double FadeOutDuration {
get { return (double)GetValue(FadeOutDurationProperty); }
set { SetValue(FadeOutDurationProperty, value); }
}

static Blink() {
// register dependency properties
OffDurationProperty = DependencyProperty.Register("OffDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(1.0));
OnDurationProperty
= DependencyProperty.Register("OnDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(1.0));
FadeInDurationProperty
= DependencyProperty.Register("FadeInDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(0.2));
FadeOutDurationProperty
= DependencyProperty.Register("FadeOutDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(0.5));
}
thing to animate is UIElement.OpacityProperty. So that's what I'm using, and to avoid a lot of fuzz with StoryBoards and stuff I set up the animation with the help of a DoubleAnimationUsingKeyFrames.




// setup the animation
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
animation.RepeatBehavior
= RepeatBehavior.Forever;

double currentSeconds = 0; // keep track of cumulated time
LinearDoubleKeyFrame start = new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(
0)));
animation.KeyFrames.Add(start);

currentSeconds
+= FadeInDuration;
LinearDoubleKeyFrame fadeInFrame
= new LinearDoubleKeyFrame(1,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(fadeInFrame);

currentSeconds
+= OnDuration;
LinearDoubleKeyFrame onFrame
= new LinearDoubleKeyFrame(1,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(onFrame);

currentSeconds
+= FadeOutDuration;
LinearDoubleKeyFrame fadeOutFrame
= new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(fadeOutFrame);

currentSeconds
+= OffDuration;
LinearDoubleKeyFrame offFrame
= new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(offFrame);

Child.BeginAnimation(UIElement.OpacityProperty, animation);



And here is the whole Blink.cs in its entirety once again:




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


namespace BlinkTest {
[ContentProperty(
"Child")]
public class Blink : Control {
UIElement m_child;

public UIElement Child {
get { return m_child; }
set {
if (m_child != null) {
RemoveVisualChild(m_child);
RemoveLogicalChild(m_child);
}
if ((m_child = value) != null) {
AddVisualChild(m_child);
AddLogicalChild(m_child);

// setup the animation
DoubleAnimationUsingKeyFrames animation = new DoubleAnimationUsingKeyFrames();
animation.RepeatBehavior
= RepeatBehavior.Forever;

double currentSeconds = 0; // keep track of cumulated time
LinearDoubleKeyFrame start = new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(
0)));
animation.KeyFrames.Add(start);

currentSeconds
+= FadeInDuration;
LinearDoubleKeyFrame fadeInFrame
= new LinearDoubleKeyFrame(1,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(fadeInFrame);

currentSeconds
+= OnDuration;
LinearDoubleKeyFrame onFrame
= new LinearDoubleKeyFrame(1,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(onFrame);

currentSeconds
+= FadeOutDuration;
LinearDoubleKeyFrame fadeOutFrame
= new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(fadeOutFrame);

currentSeconds
+= OffDuration;
LinearDoubleKeyFrame offFrame
= new LinearDoubleKeyFrame(0,
KeyTime.FromTimeSpan(TimeSpan.FromSeconds(currentSeconds)));
animation.KeyFrames.Add(offFrame);

Child.BeginAnimation(UIElement.OpacityProperty, animation);
}
}
}

protected override int VisualChildrenCount {
get { return Child != null ? 1 : 0; }
}

protected override Visual GetVisualChild(int index) {
if (index > 0 || Child == null) throw new ArgumentException("index");
return Child;
}

protected override Size MeasureOverride(Size constraint) {
Size sizeDesired
= new Size(0, 0);
if (Child != null) {
Child.Measure(constraint);
}
sizeDesired.Width
+= Child.DesiredSize.Width;
sizeDesired.Height
+= Child.DesiredSize.Height;

return sizeDesired;
}

protected override Size ArrangeOverride(Size arrangeBounds) {
if (Child != null) {
Rect rect
= new Rect(
new Point((arrangeBounds.Width - Child.DesiredSize.Width) / 2,
(arrangeBounds.Height
- Child.DesiredSize.Height) / 2),
Child.DesiredSize);
Child.Arrange(rect);
}
return arrangeBounds;
}

// Using a DependencyProperty as the backing store for OffDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OffDurationProperty;

// Using a DependencyProperty as the backing store for OnDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OnDurationProperty;

// Using a DependencyProperty as the backing store for RiseDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FadeInDurationProperty;

// Using a DependencyProperty as the backing store for DeclineDuration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FadeOutDurationProperty;

public double OnDuration {
get { return (double)GetValue(OnDurationProperty); }
set { SetValue(OnDurationProperty, value); }
}

public double OffDuration {
get { return (double)GetValue(OffDurationProperty); }
set { SetValue(OffDurationProperty, value); }
}

public double FadeInDuration {
get { return (double)GetValue(FadeInDurationProperty); }
set { SetValue(FadeInDurationProperty, value); }
}

public double FadeOutDuration {
get { return (double)GetValue(FadeOutDurationProperty); }
set { SetValue(FadeOutDurationProperty, value); }
}

static Blink() {
// register dependency properties
OffDurationProperty = DependencyProperty.Register("OffDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(1.0));
OnDurationProperty
= DependencyProperty.Register("OnDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(1.0));
FadeInDurationProperty
= DependencyProperty.Register("FadeInDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(0.2));
FadeOutDurationProperty
= DependencyProperty.Register("FadeOutDuration", typeof(double), typeof(Blink), new UIPropertyMetadata(0.5));
}
}
}


The code was developed on Vista using the RC1 bits of the .NET 3.0 SDK



Technorati tags: ,

No comments: