1

如果单击按钮,我有一个大约需要 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()渲染速度不够快,通知仅在 5 秒结束后显示Thread.Sleep

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

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

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

4

3 回答 3

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)(()=>Thread.Sleep(5000)));

于 2014-01-15T21:26:11.560 回答
1

如果您希望 UI 更新,则需要将长时间运行的操作更改为异步操作,这意味着将 UI 依赖项与任何逻辑或可能一直占用 IO 绑定的操作正确解耦。如果您不这样做,那么在您的长时间运行操作中发生的任何 UI 更新实际上都不会反映在 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 方法相比有些“倒退”,但我发现它往往会导致更清晰的逻辑。您无需使用显式调用 UI 更新来编写后台代码,而是通过Task.Run(). 这也倾向于导致以 UI 为中心的代码和底层逻辑代码之间的自然分离,因为您需要将逻辑重构为可以以这种方式调用的方法。无论如何,这对于长期可维护性来说是一个好主意。

于 2014-01-15T21:42:08.833 回答
0

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

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

但是你最好把你的计算移到后台线程中。

于 2013-03-24T12:40:17.310 回答