等到绘制UIElement(没有updatelayout)

时间:2013-03-24 12:20:14

标签: c# .net wpf uielement

如果单击一个按钮,我的功能大约需要5秒钟才能完成。如果单击该按钮,我想显示某种通知,以指示正在处理按钮,如

<Button Click="OnButtonClick" Content="Process Input" />

<Border x:Name="NotificationBorder" Opacity="0" IsHitTestVisible="False" 
        Width="500" Height="100" Background="White">
    <TextBlock Text="Your input is being processed" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>

在按钮上的代码隐藏中点击:

private void OnButtonClick(Object sender, RoutedEventArgs e)
{
    DoubleAnimation da = new DoubleAnimation
        {
            From = 5,
            To = 0,
            Duration = TimeSpan.FromSeconds(2.5),
        };

    // Making border visible in hopes that it's drawn before animation kicks in
    NotificationBorder.Opacity = 1;
    da.Completed += (o, args) => NotificationBorder.Opacity = 0;

    NotificationBorder.UpdateLayout(); //Doesn't do anything
    UpdateLayout(); // Doesn't do anything

    NotificationBorder.BeginAnimation(OpacityProperty, da);

    // Simulate calculationheavy functioncall
    Thread.Sleep(5000);
}

某种程度上UpdateLayout()的渲染速度不够快,通知只会在Thread.Sleep的5秒结束后显示。

Dispatcher.Invoke((Action)(() => NotificationBorder.Opacity = 1), DispatcherPriority.Render);也不起作用。

此外,我不能让Thread.Sleep在一个单独的工作线程中运行 - 在实际应用程序中,它需要从Dispatcher拥有的对象读取数据并(重新)构建UI的一部分。

在调用Thread.Sleep()之前有没有办法让它可见?

3 个答案:

答案 0 :(得分:1)

问题是主线程(UI线程)上的任何长活动都会导致UI冻结,因此您将看不到任何动画。

您需要在单独的线程中进行计算。我需要访问Dispatcher拥有的对象,您可以使用uiObject.Dispatcher.BeginInvoke或Invoke。 BackgroundWorker可以帮助您执行一些长计算并订阅事件Completed,这将由BackgroundWorker自动在UI线程上触发。

如果您对UI冻结没问题,请使用Dispatcher.BeginInvoke(DispatcherPriority.Background,...)更改一些属性和其他内容,例如

NotificationBorder.Opacity = 1; Dispatcher.BeginInvoke(DispatcherPriority.Background,(Action)(()=&gt; Thread.Sleep(5000)));

答案 1 :(得分:1)

如果您希望更新UI,则需要将长时间运行的操作更改为异步,这意味着正确地将UI依赖关系从任何逻辑或者IO绑定操作中解放出来。如果你不这样做,那么长时间运行的操作中发生的任何UI更新都不会实际反映在用户界面中,直到完成为止。

如果您使用的是.NET 4.5,这是我首选的模式:

    public async Task DoComplexStuff()
    {
        //Grab values from UI objects that must be accessed on UI thread

        //Run some calculations in the background that don't depend on the UI
        var result = await Task.Run((Func<string>)DoComplexStuffInBackground);

        //Update UI based on results

        //Possibly do more stuff in the background, etc.
    }

    private string DoComplexStuffInBackground()
    {
        return "Stuff";
    }

这有点“倒退”。从旧式的BackgroundWorker方法,但我发现它往往导致更清晰的逻辑。您可以通过Task.Run()的显式后台工作调用编写UI代码,而不是使用显式的UI更新调用来编写后台代码。这也倾向于导致以UI为中心的代码与底层逻辑代码之间的自然分离,因为您需要将逻辑重构为可以这种方式调用的方法。无论如何,这对于长期可维护性来说是一个好主意。

答案 2 :(得分:0)

因此,您可以将操作(UI和计算)分配到调度程序中,它们将在一行中执行

Dispatcher.BeginInvoke(do ui things);
Dispatcher.BeginInvoke(do your logic things);

但您最好将计算移到后台主题中。