Reputation: 282915
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
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
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
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