5

The app window blocks while a rendering operation is active. I.e. when the Content property of a ContentControl is set. A user control which is a DataTemplate for the content is drawn. The freeze lasts between 5 and 10 seconds depending on the PC being used.

This user control is not too complex (about 250 simple controls - images, text boxes, text blocks, buttons, etc). Layout is far from perfect, I didn't write it, and I neither have time, nor want to optimize the layout, as the problem could at best be reduced.

The best I was able to accomplish is to wrap the control in a 'container' which manages to draw a loading animation and show a busy cursor before the ui/app window freeze. I give a complete code listing for it below.

I commented 'freeze starts here' in the code, at the bottom of the question in the wrapper custom control code. That's when the WPF rendering engine starts drawing the user control (i.e. the grid inside it).

I used my favorite search engine a lot, and I learned that WPF has a special 'render' thread which is separate from the UI thread.

Other that hiding the App window while it is frozen and displaying a 'loading' animation window during that time (or some derivative of this), which is easy given the code below but absurd - is there some way to mitigate the situation?

Here is the code, first the use-case:

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

The wrapper custom control layout:

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

And wrapper custom control code:

/// <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;
        }
    }
}
h.alex
  • 902
  • 1
  • 8
  • 31
  • I tried with a `WrapPanel` with 1000+ buttons as VisualElement, and it appears instantly... Is there anything like a lot of data that needs to be loaded in the datacontexts somewhere? – Roel van Westerop Aug 28 '14 at 13:45
  • I think no data is being loaded from a source (e.g. database). A lot of data which is fetched is (being) bound though. For the weekend I will create a canvas with a million rects, that I hope will do it. – h.alex Aug 28 '14 at 23:30
  • A million is a lot, you're bound to get bad performance from WPF with that amount. And it doesn't represent the same problem you're getting with the estimated 250 controls in your usercontrol. – Roel van Westerop Aug 29 '14 at 06:44
  • I would assume a modern day gpu can render a million rects. That's my point! Even if the performance is bad, I'd live with it if there is a way to gracefully handle the rendering time. Once the control is rendered it behaves normally. It just takes a 5-10 second freeze for it to show! That's unacceptable in an enterprise environment. – h.alex Aug 29 '14 at 11:51
  • 1
    appreciated if you can support your question with a working sample which can reproduce the issue. – pushpraj Aug 29 '14 at 13:02
  • Profile it! Use the Performance Toolkit to find out whether or not it is the UI thread that is so busy. It will also tell you of superfluous animations, triggers etc. ALWAYS measure first before optimizing so you won't be implementing solutions that do not help. another quick trick is to monitor disk and network activity in case a file/database is queried by the UI thread... – Emond Aug 30 '14 at 14:22
  • have you tried freezing your freezables? http://stackoverflow.com/questions/799911/in-what-scenarios-does-freezing-wpf-objects-benefit-performance-greatly Also check this out http://wpftutorial.net/10PerformanceTips.html – Arie Sep 03 '14 at 07:51
  • Does Visual Studio report any binding failures on the Output window as it starts to freeze? Specially bindings with FindAncestor? – dharshana jagoda Sep 04 '14 at 09:58

2 Answers2

3

I really do not think that your problem is actually related to the rendering or the layout. Especially with only 250 controls as I saw wpf munching 100 times more without any problem (its rendering engine is inefficient but not that inefficient). Unless maybe if you abuse rich effects (bitmap effects, opacity mask) with a poor hardware or a poor driver, or do something really odd.

Consider all the data you need. Are there large images or other large resources to load from the disk? Network operations? Long computations?

Depending on the answer it may be possible to defer some tasks to another thread. But without more informations the only solution I can suggest is to use HostVisual to nest controls that will live in another thread. Unfortunately this is only suitable for non-interactive children (children that do not need to receive user inputs).

Victor Victis
  • 313
  • 1
  • 8
-1

From what I know there is both a render thread and ui thread, the ui thread is supposed to stay responsive until rendering is compete and then it (ui thread) completes the updating. It sounds like something else is going on, as 250 controls doesnt explain a 5 to 10 second wait.

Steve
  • 502
  • 3
  • 12