简体   繁体   English

等待直到绘制UIElement(没有updatelayout)

[英]Wait until UIElement is drawn (without updatelayout)

I have a function that takes around 5 seconds to complete if a button is clicked. 如果单击一个按钮,我的功能大约需要5秒钟才能完成。 If the button is clicked, I want to display some kind of notification to indicate that the buttonclick is being processed, something like 如果单击了按钮,我想显示某种通知以指示正在处理按钮单击,例如

<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>

And in code-behind on the button click: 然后在按钮的代码后面单击:

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);
}

Somehow UpdateLayout() isn't rendering fast enough, the notification is only displayed after the 5 seconds of Thread.Sleep are over. 不知UpdateLayout()的渲染速度不够快,仅在Thread.Sleep的5秒结束后才显示通知。

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

Additionally, I can't let Thread.Sleep run in a separate worker thread - In the real application, it needs to read data from Dispatcher-owned objects and (re)build parts of the UI. 另外,我不能让Thread.Sleep在单独的工作线程中运行-在实际的应用程序中,它需要从Dispatcher拥有的对象中读取数据,并(重新)构建UI的各个部分。

Is there a way to make it visible before Thread.Sleep() is called? 在调用Thread.Sleep()之前是否有办法使其可见?

The problem is any long activity on main thread (UI thread) will lead freeze in UI, so you will not see any animation. 问题是主线程(UI线程)上的任何长时间活动都会导致UI冻结,因此您将看不到任何动画。

You need to do your calculations in a separate thread. 您需要在单独的线程中进行计算。 I you need to access to Dispatcher-owned objects you can use uiObject.Dispatcher.BeginInvoke or Invoke. 我需要访问可以使用uiObject.Dispatcher.BeginInvoke或Invoke的Dispatcher拥有的对象。 BackgroundWorker can help you the way to execute some long calculations and subscribe to the event Completed which will be fired by BackgroundWorker on the UI thread automatically. BackgroundWorker可以帮助您执行一些长时间的计算并订阅事件Completed,该事件将由BackgroundWorker在UI线程上自动触发。

If you are ok with UI freeze, change some property and other stuff re-schedule with Dispatcher.BeginInvoke(DispatcherPriority.Background,...), like 如果您可以接受UI冻结,则可以使用Dispatcher.BeginInvoke(DispatcherPriority.Background,...)更改一些属性和其他重新安排的时间表,例如

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

If you want the UI to update, you need to change your long-running operation to be asynchronous, which means properly decoupling the UI dependencies from whatever logic or perhaps IO-bound operations are taking all the time. 如果要更新UI,则需要将长时间运行的操作更改为异步操作,这意味着将UI依赖项与所有耗时的逻辑或IO绑定操作正确地分离。 If you don't do this, none of the UI updates occurring in your long-running operation will actually be reflected in the UI until it's done, anyway. 如果您不这样做,那么在长时间运行的操作中发生的任何UI更新实际上都不会反映在UI中,直到完成为止。

If you're running with .NET 4.5, here is my preferred pattern for how to do this: 如果您使用的是.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";
    }

This is somewhat 'backwards' from the older style BackgroundWorker approach, but I find it tends to result in cleaner logic. 这与较旧的BackgroundWorker方法相比有些“落后”,但我发现它倾向于产生更清晰的逻辑。 Instead of writing your background code, with explicit Invokes for UI updates, you write your UI code around explicit background work calls via Task.Run() . 您无需通过显式调用UI更新来编写背景代码,而是通过Task.Run()在显式后台工作调用周围编写UI代码。 This also tends to lead to a natural separation between the UI-centric code and the underlying logic code, as you need to refactor your logic into methods that can be called in this way. 这也往往导致以UI为中心的代码与基础逻辑代码之间的自然分离,因为您需要将逻辑重构为可以通过这种方式调用的方法。 That's a good idea for long-term maintainability anyway. 无论如何,这对于长期可维护性是个好主意。

So you can dispatch both action (UI and your calculations) into the dispatcher and they will be executed in a line 因此,您可以将动作(UI和您的计算)都分派到分派器中,它们将在一行中执行

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

But you'd better move your calculation into the background thread. 但是您最好将计算移入后台线程。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM