mpen
mpen

Reputation: 282915

WPF Fade out status bar text after X seconds?

I'm trying to come up with an unobtrusive way to to display minor error messages to a user. So I've added a statusbar to my form,

    <StatusBar Margin="0,288,0,0" Name="statusBar" Height="23" VerticalAlignment="Bottom">
        <TextBlock Name="statusText">Ready.</TextBlock>
    </StatusBar>

And then when they click an "Add" button, it should do some stuff, or display an error message:

private void DownloadButton_Click(object sender, RoutedEventArgs e)
{
    addressBar.Focus();
    var url = addressBar.Text.Trim();
    if (string.IsNullOrEmpty(url))
    {
        statusText.Text = "Nothing to add.";
        return;
    }
    if (!url.Contains('.'))
    {
        statusText.Text = "Invalid URL format.";
        return;
    }
    if (!Regex.IsMatch(url, @"^\w://")) url = "http://" + url;
    addressBar.Text = "";

But the message just sits there for the life of the app... I think I should reset it after about 5 seconds... how can I set a timer to do that?

Bonus: How do I give it a nifty fade-out effect as I do so?


I've created a System.Timers.Timer,

    private Timer _resetStatusTimer = new Timer(5000);

    void _resetStatusTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        statusText.Text = "Ready";
    }

But the Elapsed event runs on a different thread than the UI, which it doesn't like... how to I get around that?

Upvotes: 20

Views: 15289

Answers (3)

baltermia
baltermia

Reputation: 1343

await Task.Delay(5000); // Wait 5 seconds

for (int i = 99; i >= 0; i--)
{
    statusText.Opacity = i / 100d;

    await Task.Delay(15); // The animation will take 1.5 seconds
}

statusText.Visibility = statusText.Hidden;

statusText.Opacity = 1;

If you want to show statusText again, you have to sets it's Visibility to Visible:

statusText.Visibility = Visibility.Visible;

Also make sure to add the async modifier to your methods signature, otherwise the compiler would show you an error relating the await operator. await is used that the UI still responds while the text is fading out.

Upvotes: 1

as-cii
as-cii

Reputation: 13019

You can use a Storyboard to do the trick.

<Storyboard x:Key="Storyboard1">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="statusBarItem">
            <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
            <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
            <EasingDoubleKeyFrame KeyTime="0:0:3" Value="1"/>
            <EasingDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>

When the message has to be displayed you just call programmatically the Begin method of the StoryBoard or insert a trigger as below.

<Window.Triggers>
    <EventTrigger RoutedEvent="TextBoxBase.TextChanged" SourceName="textBox">
        <BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
    </EventTrigger>
</Window.Triggers>

EDIT: Another option is to do like this:

<TextBlock Name="statusText" Text="{Binding Path=StatusBarText, NotifyOnTargetUpdated=True}">
        <TextBlock.Triggers>
            <EventTrigger RoutedEvent="Binding.TargetUpdated">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
                            <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                            <EasingDoubleKeyFrame KeyTime="0:0:0.25" Value="1"/>
                            <EasingDoubleKeyFrame KeyTime="0:0:4" Value="1"/>
                            <EasingDoubleKeyFrame KeyTime="0:0:5" Value="0"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </TextBlock.Triggers>

Then to create a DependencyProperty called StatusBarText in this case that is implemented as follow:

public string StatusBarText
    {
        get { return (string)GetValue(StatusBarTextProperty); }
        set { SetValue(StatusBarTextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StatusBarText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StatusBarTextProperty =
        DependencyProperty.Register("StatusBarText", typeof(string), typeof(MyOwnerClass), new UIPropertyMetadata(""));

Hope this helps.

Upvotes: 38

Aphex
Aphex

Reputation: 7490

Your timer is a good approach, and you even identified your problem: you just need a way to access statusText.Text on the UI thread. (In WPF, threads other than the UI thread are prohibited from accessing UI elements). Here comes the WPF Dispatcher to the rescue:

http://msdn.microsoft.com/en-us/magazine/cc163328.aspx#S5

You can use the DispatcherTimer class to do exactly what you were attempting (here's their code):

// Create a Timer with a Normal Priority
_timer = new DispatcherTimer();

// Set the Interval to 2 seconds
_timer.Interval = TimeSpan.FromMilliseconds(2000); 

// Set the callback to just show the time ticking away
// NOTE: We are using a control so this has to run on 
// the UI thread
_timer.Tick += new EventHandler(delegate(object s, EventArgs a) 
{ 
    statusText.Text = string.Format(
        "Timer Ticked:  {0}ms", Environment.TickCount); 
});

// Start the timer
_timer.Start();

Upvotes: 8

Related Questions