
Introduction
This is the seventh article in the WPFSpark series. Till now I have covered five controls
in WPFSpark - SprocketControl
, ToggleSwitch
, FluidWrapPanel
, SparkWindow
, FluidPivotPanel
, and FluidProgressBar
.
The previous articles in the WPFSpark series can be accessed here:
- WPFSpark: 1 of n: SprocketControl
- WPFSpark: 2 of n: ToggleSwitch
- WPFSpark: 3 of n: FluidWrapPanel
- WPFSpark: 4 of n: SparkWindow
- WPFSpark: 5 of n: FluidPivotPanel
- WPFSpark: 6 of n: FluidProgressBar
In this article, I describe in detail the seventh control in this library - the FluidStatusBar
control.

Inspiration
The need for FluidStatusBar
arose when I was creating a small application for use at my workplace. In this tool, processing of data took some time,
so instead of making the user stare at an indeterminate progress bar (SprocketControl
in this case!) for a long time,
I thought of providing short messages to the user at various stages of the processing. So I added a TextBlock
in my application to display the processing status message.
Once this was accomplished, I felt that just updating the text of the TextBlock
seemed so static and boring! There is no indication to the user that the status message
is updating and the text change happens in the blink of the eye. So, in order to make the status updates more dynamic and exciting, the idea of FluidStatusBar
was born!
In FluidStatusBar
, whenever the status is updated, the previous status message slides out and fades out. At the same time, the new message fades in.
This has a better visual appeal when compared to the static update of the TextBlock
. FluidStatusBar
can also be made to update the status without any animations.
FluidStatusBar Demystified
StatusDirection
StatusDirection
is an enum
which defines the various directions in which the old status message can slide out when a new status message arrives.
public enum StatusDirection
{
Left = 0,
Right = 1,
Up = 2,
Down = 3
}
StatusMessage
This class encapsulates the message that needs to be displayed in the FluidStatusBar
.
public class StatusMessage
{
#region Properties
public string Message { get; set; }
public bool IsAnimated { get; set; }
#endregion
#region Construction / Initialization
public StatusMessage()
{
}
public StatusMessage(string message, bool isAnimated = false)
{
Message = message;
IsAnimated = isAnimated;
}
#endregion
}
StatusMessage Properties
Dependency Property | Type | Description | Default Value |
Message | String | Gets or sets the status message that needs to be displayed in the FluidStatusBar . | String.Empty |
IsAnimated | Boolean | Gets or sets the flag which indicates whether the old status message must slide out when the new status message arrives. | false |
FluidStatusBar
The FluidStatusBar
consists of two overlapped TextBlock
s - FadeOutTextBlock
and FadeInTextBlock
.
Both have the same properties except the Text
property. Also, initially both FadeOutTextBlock
and FadeInTextBlock
have their Visibility
set to Collapsed
. FadeOutTextBlock
is located on top of the FadeInTextBlock
.
FluidStatusBar
maintains a Queue
of StatusMessage
s (called messageQueue
) so that they are processed
on a first-come-first-serve basis. Whenever a new StatusMessage
arrives, either by setting the Message
property
of the FluidStatusBar
or by calling the SetStatus()
method, the StatusMessage
object gets added to the messageQueue
.
Then the ProcessAnimationQueue()
method is called to process the items available in the messageQueue
.
In the ProcessAnimationQueue()
method, the first StatusMessage
is inspected. If the IsAnimated
property
is set to true
, then the old status message which was set in FadeInTextBlock
's Text
property is now set to FadeOutTextBlock
's
Text
property. FadeInTextBlock
's Text
property is then set to the new status message. Then the Storyboard
s responsible
for animating the sliding out of the FadeOutTextBlock
and the fading in of the FadeInTextBlock
begin. If the IsAnimated
property is set to
false
, the FadeInTextBlock
's Text
property is simply set to the new status message. When the animation completes, the first StatusMessage
in the messageQueue
is dequeued and the whole process repeats.
public partial class FluidStatusBar : UserControl
{
#region Fields
Storyboard fadeInOutSB = null;
Storyboard fadeInSB = null;
Storyboard fadeOutLeftSB = null;
Storyboard fadeOutRightSB = null;
Storyboard fadeOutUpSB = null;
Storyboard fadeOutDownSB = null;
Queue<StatusMessage> messageQueue = null;
bool isAnimationInProgress = false;
#endregion
#region DependencyProperties
...
#endregion
#region Construction / Initialization
public FluidStatusBar()
{
InitializeComponent();
messageQueue = new Queue<StatusMessage>();
fadeOutLeftSB = (Storyboard)this.Resources["FadeInOutLeftStoryboard"];
if (fadeOutLeftSB != null)
{
fadeOutLeftSB.Completed += new EventHandler(OnFadeOutAnimationCompleted);
}
fadeOutRightSB = (Storyboard)this.Resources["FadeInOutRightStoryboard"];
if (fadeOutRightSB != null)
{
fadeOutRightSB.Completed += new EventHandler(OnFadeOutAnimationCompleted);
}
fadeOutUpSB = (Storyboard)this.Resources["FadeInOutUpStoryboard"];
if (fadeOutUpSB != null)
{
fadeOutUpSB.Completed += new EventHandler(OnFadeOutAnimationCompleted);
}
fadeOutDownSB = (Storyboard)this.Resources["FadeInOutDownStoryboard"];
if (fadeOutDownSB != null)
{
fadeOutDownSB.Completed += new EventHandler(OnFadeOutAnimationCompleted);
}
fadeInSB = (Storyboard)this.Resources["FadeInStoryboard"];
if (fadeInSB != null)
{
fadeInSB.Completed += new EventHandler(OnFadeOutAnimationCompleted);
}
fadeInOutSB = fadeOutLeftSB;
isAnimationInProgress = false;
}
#endregion
#region APIs
public void SetStatus(StatusMessage statusMsg)
{
if (statusMsg == null)
return;
lock (messageQueue)
{
messageQueue.Enqueue(statusMsg);
}
ProcessAnimationQueue();
}
public void SetStatus(string message, bool isAnimated)
{
lock (messageQueue)
{
messageQueue.Enqueue(new StatusMessage(message, isAnimated));
}
ProcessAnimationQueue();
}
#endregion
#region Helpers
private void UpdateFadeOutDistance(Storyboard sb, Thickness thickness)
{
if (sb != null)
{
foreach (Timeline timeline in sb.Children)
{
ThicknessAnimation anim = timeline as ThicknessAnimation;
if (anim != null)
{
anim.SetValue(ThicknessAnimation.ToProperty, thickness);
}
}
}
}
private void UpdateMoveDuration(Storyboard sb, Duration duration)
{
if (sb != null)
{
foreach (Timeline timeline in sb.Children)
{
ThicknessAnimation anim = timeline as ThicknessAnimation;
if (anim != null)
{
anim.SetValue(ThicknessAnimation.DurationProperty, duration);
}
}
}
}
private void UpdateFadeOutDuration(Storyboard sb, Duration duration)
{
if (sb != null)
{
foreach (Timeline timeline in sb.Children)
{
DoubleAnimation anim = timeline as DoubleAnimation;
if (anim != null)
{
anim.SetValue(DoubleAnimation.DurationProperty, duration);
}
}
}
}
private void ProcessAnimationQueue()
{
if (isAnimationInProgress)
return;
lock (messageQueue)
{
if (messageQueue.Count > 0)
{
Dispatcher.BeginInvoke(new Action(() =>
{
if (messageQueue.Count > 0)
{
isAnimationInProgress = true;
StatusMessage msg = messageQueue.Peek() as StatusMessage;
if (msg != null)
{
if (msg.IsAnimated)
{
FadeOutTextBlock.Text = FadeInTextBlock.Text;
FadeInTextBlock.Text = msg.Message;
if (fadeInOutSB != null)
fadeInOutSB.Begin();
}
else
{
FadeInTextBlock.Text = msg.Message;
if (fadeInSB != null)
fadeInSB.Begin();
}
}
}
}));
}
}
}
#endregion
#region Event Handlers
void OnFadeOutAnimationCompleted(object sender, EventArgs e)
{
isAnimationInProgress = false;
lock (messageQueue)
{
if (messageQueue.Count > 0)
messageQueue.Dequeue();
}
ProcessAnimationQueue();
}
#endregion
}
FluidStatusBar Properties
Dependency Property | Type | Description | Default Value |
FadeOutDirection | WPFSpark.StatusDirection | Gets or sets the direction in which the old status message should slide out when the new status message arrives. | WPFSpark.StatusDirection.Left |
FadeOutDistance | Double | Gets or sets the distance to which the old status message should slide out when the new status message arrives. | 0.0 |
FadeOutDuration | Duration | Gets or sets the duration for the fading out animation. | 0 |
MoveDuration | Duration | Gets or sets the duration for the sliding out animation. | 0 |
TextHorizontalAlignment | HorizontalAlignment | Gets or sets the HorizontalAlignment of the TextBlock s displaying the status message. | HorizontalAlignment.Center |
TextVerticalAlignment | VerticalAlignment | Gets or sets the VerticalAlignment of the TextBlock s displaying the status message. | VerticalAlignment.Center |
Message | WPFSpark.StatusMessage | Gets or sets the StatusMessage that has to be displayed. | null |
History
- December 21, 2011: WPFSpark v1.0 released.