问题
At some point in my Silverlight application I need to perform a heavy operation which freezes the UI thread for about 4 seconds. Before actually performing the operation I am trying to display a simple text indicator via a TextBlock
control.
StatusTextBlock.Text = "Performing Some Operation...";
System.Threading.Thread.Sleep(4000); // Just as an example
The problem is that the UI thread freezes before the text of the TextBlock
control gets updated. How can I get the notification text shown before the operation begins?
Also, taking the heavy operation to a background thread is not an option for me, as it deals with UI objects (it switches the visual root of the application) and should be executed on the UI thread.
回答1:
My suggestion is to take it off UI thread and use background thread...
StatusTextBox.Text = "Before Sleep";
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
void bw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(8000);}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
StatusTextBox.Text = "after Sleep";
}
回答2:
I found a solution with the help of Jeff Prosise's blog post: http://www.wintellect.com/cs/blogs/jprosise/archive/2008/10/25/cool-silverlight-trick-5.aspx
The idea is to delay the call performing a long running task till a Silverlight UI rendering event fires. For this I used the CompositionTarget.Rendering
event. I subscribed to it in the constructor of the user control:
CompositionTarget.Rendering += this.CompositionTargetRendering;
After I update the text of a TextBlock
control I set a private flag, which indicates that some processing should be made in the event handler:
StatusTextBlock.Text = "Performing Some Operation...";
this.processRenderingEvent = true;
And here is the code of the handler:
private void CompositionTargetRendering(Object sender, EventArgs e)
{
if (this.processRenderingEvent)
{
if (++this.renderingEventCounter == 2)
{
System.Threading.Thread.Sleep(4000); // Example of long running task
this.processRenderingEvent = false;
}
}
}
An important point to mention here is that I use a private integer field renderingEventCounter
to begin the long running task not the first time the event fires, but the second. The reason for this is that the CompositionTarget.Rendering
event is fired just before the Silverlight UI rendering engine draws a new frame on the application's display surface, which means that at the first time the event fires the text of the TextBlock
control is not yet updated. But it will be updated the second time.
回答3:
I think you should implement the BackgroundWorker thread is tsiom's answer, but use the Dispatcher.BeginInvoke to operate on the UI objects, here is a MSDN article on how to use the method: http://msdn.microsoft.com/en-us/library/cc190824%28v=vs.95%29.aspx
Also, see another StackOverflow question for a more comprehensive scenario using the Dispatcher: Understanding the Silverlight Dispatcher
回答4:
I just ran into this situation myself. The problem (I think) is that before the text gets updated you have already begun the intensive operation, so you have to wait.
What you can do is to attach a listened to some method on the textbox that only gets called once the text is updated (textChanged perhaps?) and THEN call your intensive operation.
This seems hackish to me though...
回答5:
This is ugly but it works. By delaying the initiliazation of the long running operation using a DispatcherTimer we can allow the UI to be updated before the operation is started.
XAML:
<UserControl x:Class="SilverlightApplication13.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="LayoutRoot"
Background="White">
<StackPanel>
<Border x:Name="Brd01"
Visibility="Collapsed"
Background="Red">
<TextBlock VerticalAlignment="Center"
Margin="30">Sleeping for 4 seconds...</TextBlock>
</Border>
<Border x:Name="Brd02"
Visibility="Collapsed"
Background="Lime">
<TextBlock VerticalAlignment="Center"
Margin="30">Done!</TextBlock>
</Border>
<Button Content="Start Operation"
Click="Button_Click_1"></Button>
</StackPanel>
</Grid>
</UserControl>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace SilverlightApplication13
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Show the "working..." message
Brd01.Visibility = System.Windows.Visibility.Visible;
//Initialize a timer with a delay of 0.1 seconds
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(100);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
//Start the long running operation
Thread.Sleep(4000);
Brd01.Visibility = System.Windows.Visibility.Collapsed;
Brd02.Visibility = System.Windows.Visibility.Visible;
//Kill the timer so it will only run once.
(sender as DispatcherTimer).Stop();
(sender as DispatcherTimer).Tick -= Timer_Tick;
}
}
}
来源:https://stackoverflow.com/questions/15931032/showing-a-text-indicator-before-freezing-the-silverlight-ui-thread