在我的Silverlight应用程序的某些时候,我需要执行一个繁重的操作,冻结UI线程大约4秒钟。在实际执行操作之前,我试图通过TextBlock
控件显示简单的文本指示符。
StatusTextBlock.Text = "Performing Some Operation...";
System.Threading.Thread.Sleep(4000); // Just as an example
问题是UI线程在TextBlock
控件的文本更新之前冻结。如何在操作开始前显示通知文本?
此外,对于我来说,将繁重的操作转移到后台线程不是一个选项,因为它处理UI对象(它切换应用程序的可视根)并且应该在UI线程上执行。
答案 0 :(得分:1)
我的建议是将其从UI线程中删除并使用后台线程......
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";
}
答案 1 :(得分:1)
我在Jeff Prosise的博客文章http://www.wintellect.com/cs/blogs/jprosise/archive/2008/10/25/cool-silverlight-trick-5.aspx
的帮助下找到了解决方案这个想法是延迟调用执行长时间运行的任务,直到Silverlight UI呈现事件触发。为此,我使用了CompositionTarget.Rendering
事件。我在用户控件的构造函数中订阅了它:
CompositionTarget.Rendering += this.CompositionTargetRendering;
更新TextBlock
控件的文本后,我设置了一个私有标志,表示应该在事件处理程序中进行一些处理:
StatusTextBlock.Text = "Performing Some Operation...";
this.processRenderingEvent = true;
这是处理程序的代码:
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;
}
}
}
这里要提到的重点是我使用私有整数字段renderingEventCounter
来开始长时间运行的任务,而不是第一次触发事件,而第二次。这样做的原因是,在Silverlight UI呈现引擎在应用程序的显示表面上绘制新帧之前触发了CompositionTarget.Rendering
事件,这意味着在事件第一次触发{{1}的文本时}控件尚未更新。但它会第二次更新。
答案 2 :(得分:0)
我认为你应该实现BackgroundWorker线程是tsiom的答案,但是使用Dispatcher.BeginInvoke来操作UI对象,这里有一篇关于如何使用该方法的MSDN文章:http://msdn.microsoft.com/en-us/library/cc190824%28v=vs.95%29.aspx
另外,请参阅另一个StackOverflow问题,了解使用Dispatcher的更全面的方案:Understanding the Silverlight Dispatcher
答案 3 :(得分:0)
我自己也遇到过这种情况。问题(我认为)是在文本更新之前你已经开始密集操作,所以你必须等待。
你可以做的是在文本框上附加一个只听到文本更新后调用的方法(也许是textChanged?),然后调用你的密集操作。
虽然这对我来说似乎很苛刻......
答案 4 :(得分:0)
这很难看,但确实有效。通过使用DispatcherTimer延迟长时间运行操作的初始化,我们可以允许在操作开始之前更新UI。
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>
代码隐藏:
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;
}
}
}