WPF窗口(ui)被长时间渲染操作阻塞-可以使用后台线程进行渲染

本文关键字:可以使 后台 线程 操作 ui 窗口 WPF 长时间 | 更新日期: 2023-09-27 18:19:23

当渲染操作处于活动状态时,应用程序窗口阻塞。例如,当ContentControl的Content属性被设置时。绘制一个用户控件,它是内容的DataTemplate。冻结时间在5到10秒之间,具体取决于所使用的PC。

这个用户控件不是太复杂(大约250个简单的控件-图像,文本框,文本块,按钮等)。布局远非完美,我没有写,我既没有时间,也不想优化布局,因为问题最多可以减少。

我所能做到的最好的是将控件包装在一个"容器"中,该容器可以在ui/应用程序窗口冻结之前绘制加载动画并显示繁忙的光标。我在下面给出了完整的代码清单。

我在代码中注释了"冻结从这里开始",在包装器自定义控件代码的问题底部。这时WPF渲染引擎开始绘制用户控件(即其中的网格)。

我经常使用我最喜欢的搜索引擎,我了解到WPF有一个特殊的"渲染"线程,它与UI线程是分开的。

当应用程序窗口被冻结时隐藏它并在此期间显示"加载"动画窗口(或此的衍生品),这很容易给出下面的代码,但荒谬的-是否有一些方法来缓解这种情况?

下面是代码,首先是用例:

<!-- while I am being rendered, I block the UI thread. -->
<UserControl x:Class="MyUserControl"
             xmlns:loading="clr-namespace:Common.UI.Controls.Loading;assembly=Common.UI.Controls">    
    <loading:VisualElementContainer>
        <loading:VisualElementContainer.VisualElement>
            <Grid>
                <!-- some 500 lines of using other controls with binding, templates, resources, etc.. 
                for the same effect try having a canvas with maaany rectangles..-->
            </Grid>
        </loading:VisualElementContainer.VisualElement>
    </loading:VisualElementContainer>    
</UserControl>

包装器自定义控件布局:

<Style TargetType="{x:Type loading:VisualElementContainer}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type loading:VisualElementContainer}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <loading:LoadingAnimation x:Name="LoadingAnimation" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        <ContentControl x:Name="ContentHost"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

和包装器自定义控件代码:

/// <summary>Hosts the visual element and displays a 'loading' animation and busy cursor while it is being rendered.</summary>
public class VisualElementContainer : Control
{
    static VisualElementContainer()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(VisualElementContainer), new FrameworkPropertyMetadata(typeof(VisualElementContainer)));
    }
    private Window MyWindow;
    private ContentControl ContentHost;
    private LoadingAnimation LoadingAnimation;
    public override void OnApplyTemplate()
    {
        this.ContentHost = this.GetTemplateChild("ContentHost") as ContentControl;
        this.LoadingAnimation = this.GetTemplateChild("LoadingAnimation") as LoadingAnimation;
        base.OnApplyTemplate();
        this.MyWindow = this.FindVisualParent(typeof(Window)) as Window;
        this.SetVisual(this.VisualElement);
    }
    private static DependencyProperty VisualElementProperty =
        DependencyProperty.Register(
            "VisualElement",
            typeof(FrameworkElement),
            typeof(VisualElementContainer),
            new PropertyMetadata(null, new PropertyChangedCallback(VisualElementPropertyChanged)));
    public FrameworkElement VisualElement
    {
        get { return GetValue(VisualElementProperty) as FrameworkElement; }
        set { SetValue(VisualElementProperty, value); }
    }
    private static void VisualElementPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var me = sender as VisualElementContainer;
        if (me == null || me.ContentHost == null || me.LoadingAnimation == null)
            return;
        me.RemoveVisual(e.OldValue as FrameworkElement);
        me.SetVisual(e.NewValue as FrameworkElement);
    }
    private void RemoveVisual(FrameworkElement fwElement)
    {
        this.ContentHost.Content = null;
        if (fwElement != null)
            fwElement.Loaded -= fwElement_Loaded;
    }
    private void SetVisual(FrameworkElement fwElement)
    {
        if (fwElement == null)
        {
            this.ContentHost.Content = fwElement;
        }
        else
        {
            fwElement.Loaded += fwElement_Loaded;
            this.SetContentVisibility(false);
            this.Dispatcher
                .BeginInvoke(
                //freeze begins here
                    new Action(() => this.ContentHost.Content = fwElement),
                    System.Windows.Threading.DispatcherPriority.ContextIdle);
        }
    }
    private void fwElement_Loaded(object sender, RoutedEventArgs e)
    {
        this.SetContentVisibility(true);
        //freeze ends here.
    }
    private void SetContentVisibility(bool isContentVisible)
    {
        if (isContentVisible)
        {
            this.MyWindow.Cursor = Cursors.Arrow;
            this.LoadingAnimation.Visibility = Visibility.Collapsed;
            this.ContentHost.Visibility = Visibility.Visible;
        }
        else
        {
            this.MyWindow.Cursor = Cursors.Wait;
            this.ContentHost.Visibility = Visibility.Hidden; //Setting to collapsed results in the loaded event never fireing. 
            this.LoadingAnimation.Visibility = Visibility.Visible;
        }
    }
}

WPF窗口(ui)被长时间渲染操作阻塞-可以使用后台线程进行渲染

我真的不认为你的问题实际上与渲染或布局有关。特别是在只有250个控件的情况下,我看到wpf的运行速度是原来的100倍,没有任何问题(它的渲染引擎效率很低,但不是低效)。除非你滥用丰富的效果(位图效果,不透明度蒙版)与一个糟糕的硬件或驱动程序,或者做一些非常奇怪的事情。

考虑你需要的所有数据。是否需要从磁盘加载大型映像或其他大型资源?网络运营?长时间计算?

根据答案,可以将一些任务推迟到另一个线程。但没有更多的信息,我可以建议的唯一解决方案是使用HostVisual嵌套控件,将生活在另一个线程。不幸的是,这只适用于非交互式的子元素(不需要接收用户输入的子元素)。

据我所知,有两个渲染线程和ui线程,ui线程应该保持响应,直到渲染竞争,然后它(ui线程)完成更新。听起来好像有别的事情在发生,因为250个控件并不能解释5到10秒的等待。

相关文章: