DocumentWindow.Activate takes long time

Docking/MDI for WPF Forum

Posted 8 years ago by Mick
Version: 11.1.0545
Platform: .NET 4.0
Environment: Windows Vista (32-bit)
Avatar
Hello,

I believe I have reproduced the issue of the call to DocumentWindow.Activate taking a long time referenced here:

Here is my sample: For the DocumentWindow content, I use a StackPanel with 2000 TextBlocks to eliminate any 3rd party controls that may be an issue (but representing a 3rd party control like a data grid with 200 rows and 10 columns).

About the sample:
  1. I have placed the same control in a WPF grid next to it for reference
  2. DocumentWindow.Activate performs on average 5-100 times worse than the standard WPF Grid (performance seems to degrade depending on what other programs are open on the system)
  3. Restarting the app and testing in the opposite order to minimize the pre-compilation / assembly loading mentioned above has minimal effect
  4. Switching between DocumentWindows still shows a speed hit (click the "Create new DocumentWindow" button multiple times and switch)
  5. Sometimes, when multiple WPF applications are open (like multiple instances of VS2010), the entire application is less responsive with a populated DocumentWindow as opposed to a populated System.Windows.Controls.Grid (try resizing the sample by dragging its border). This is less reproducible than the time issues - I've reproduced it on 2/4 machines that I've tested on, but the speed issue is present on all 4.

<Window
    x:Class="ActivateWindow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:docking="http://schemas.actiprosoftware.com/winfx/xaml/docking"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <docking:DockSite Name="docksite">
            <docking:Workspace>
                <docking:TabbedMdiHost>
                    <docking:TabbedMdiContainer />
                </docking:TabbedMdiHost>
            </docking:Workspace>
        </docking:DockSite>
        <Grid x:Name="DockingGrid" Grid.Column="1" />
        <Button Grid.Row="1" Content="Create new DocumentWindow with content" Margin="2" Click="ShowDocumentWindow" />
        <Button Grid.Row="1" Grid.Column="1" Content="Create content directly" Margin="2" Click="SetGridChild" />
    </Grid>
</Window>

using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using ActiproSoftware.Windows.Controls.Docking;

namespace ActivateWindow
{
    public partial class MainWindow : Window
    {
        public MainWindow() { InitializeComponent(); }

        private void ShowDocumentWindow(object sender, RoutedEventArgs e)
        {
            MyUserControl myUserControl = new MyUserControl();

            // Create a DocumentWindow and set its content
            DocumentWindow documentWindow = new DocumentWindow(docksite, "window", "window", null, null);
            documentWindow.Content = myUserControl;

            // Time the DocumentWindow.Activate function
            Stopwatch stopwatch = Stopwatch.StartNew();
            documentWindow.Activate();
            MessageBox.Show(stopwatch.Elapsed.ToString(), "Layout Time: DocumentWindow.Activate");
        }

        private void SetGridChild(object sender, RoutedEventArgs e)
        {
            MyUserControl myUserControl = new MyUserControl();

            // Time the WPF engine layout with built in controls
            Stopwatch stopwatch = Stopwatch.StartNew();
            DockingGrid.Children.Clear();
            DockingGrid.Children.Add(myUserControl);
            MessageBox.Show(stopwatch.Elapsed.ToString(), "Layout Time: WPF standard controls");
        }
    }

    public class MyUserControl : UserControl
    {
        public MyUserControl()
        {
            StackPanel sp = new StackPanel();
            for (int i = 0; i < 2000; i++)
                sp.Children.Add(new TextBlock { Text = "TextBlock " + i });
            this.Content = new ScrollViewer { Content = sp };
        }
    }
}
Can you reproduce this and is there something I can do?

Comments (7)

Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Mick,

When I run your sample as-is, I get a time difference of about 260ms (0.0077847 for the Grid and 0.2701445 for the DocumentWindow). As there is much more going on with our Docking product, this is to be expected.

If I increase the number of TextBlocks to 200000, then I get a time of 0.8309524 for the Grid when in reality it took closer to 20 seconds. So effectively, the timing code is your sample is not accurate. You are only measuring the time it takes to add the visual to the Grid, not the time it takes to measure, arrange, and display it.

If you add a call to "DockingGrid.UpdateLayout()" before you stop the time, then that is a more realistic comparision. With that change in and 2000 visuals, the time difference is closer to 100ms (0.1793768 for the Grid and similar times as above for DocumentWindow).

Increasing the number of visuals to 200000 does show a larger difference of about 3.5 seconds (~24 seconds for Grid and ~27.5 for DocumentWindow), but using a profiler does not indicate any hot spots that could be a problem.


Actipro Software Support

Posted 8 years ago by Mick
Avatar
Thank you,

I tested it with the call to UpdateLayout() to make my timing more accurate, and I see the speed difference of ~100 ms that you mentioned.

In your last test with 200000 visuals, I would not expect to see more than the initial 100ms difference: the forum post I referenced in my first post indicated that the docking control itself will load "about instantly." However, your test indicates that the docking control's load time is dependent on its content.

Is this the case? Can you offer a suggestion for how to maintain a static load time for a DocumentWindow?

Mick
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Mick,

We don't have anything special in the code that looks at the content. A portion of the time is due to inherited attached properties being passed down, but even with those removed there isn't much improvement.

There are more visuals and much more logic involved with our Docking product, so expecting it to perform identically to a Grid isn't really a fair comparison. Any number of the visuals may be measure or arranged by WPF, which may or may not require your content to be remeasured (or atleast see if it needs to be remeasured, which also adds time).

Again, looking at the profiler results nearly all of the time is in WPF methods (for arranging and measure). We've tried several things with no success.


Actipro Software Support

Posted 8 years ago by Mick
Avatar
Thank you,

I apologize, in my response I wasn't trying to make a direct comparison between timing of your docking product and a WPF Grid. Obviously you're doing more complicated UI layout.

I was trying to make sense of what I observed (and you verified): the time spent laying out the DocumentWindow portion only - not the content - during a call to DocumentWindow.Activate was dependent upon its content (100ms for 2K visuals and 3500 ms for 200K visuals).

When I rearrange the sample to call Activate first and set its content second, the I see a constant 50-200 ms "extra" rendering time for the DocumentWindow vs. a WPF Grid regardless of the number of items it contains - getting rid of the render-time dependency on the content.

I can post or email a sample if this can't be recreated on your side.

Mick
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Mick,

By calling the Activate method, you are effectively attaching the DocumentWindow and it's content to the visual tree. When you set the Content property before you active the DocumentWindow you are effectively building the visual tree bottom-up (atleast for that part). This is versus top-down, which is the case when you set the Content after the DocumentWindow has been activated/attached.

One WPF optimization mentioned by Microsoft is to build your trees top-down (see http://msdn.microsoft.com/en-us/library/aa970683(v=vs.85).aspx#layout_and_design). Effectively, if something needs to be invalidated on the DocumentWindow, then that may be passed down to the visuals. Since there are more visuals, there is more work.

With the Grid there is less chance that things need to be invalidated or passed down.


Actipro Software Support

Posted 8 years ago by Mick
Avatar
Thank you for the clarification.

If it is a MS recommendation to build your trees "top down," would it be possible for Actipro to internally detach / reattach a DocumentWindow's content before and after a call to Activate()?

i.e. when I call my code:

DocumentWindow dw1 = new DocumentWindow(...)
dw1.Content = new VisuallyIntensiveControl();
dw1.Activate();
It would do something like this inside of the Actipro code:

public partial class DocumentWindow
{
    public void Activate()
    {
        // Get a reference to the content
        object content = this.Content;
        // null out the content
        this.Content = null;

        UpdateLayout();
        // Do other internal UI stuff

        // Set the content back as a final step
        this.Content = content;
    }
}
I have my code that deals with Navigation centralized in my project, so I would only do it in one place, but is there a reason Actipro isn't taking advantage of this potential 50-200 ms speed increase (per our examples)?

Thanks
Mick
Posted 8 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Mick,

Unfortunately, it is not that simple. We'd have to take ContentTemplate, ContentTemplateSelector, etc into account as well. These properties could also be set via Bindings or DynamicResource. With bindings we may not be able to properly save and restore the same binding either, as there are cases were the ElementName may not work if re-evaluated.

I've marked down a TODO item to see about adding such a feature in the future though. We could potentially coerce the properties to null until it's ready.

You could implement something similar in a custom ContentControl that wraps your content, then use that as the DocumentWindow content.


Actipro Software Support

The latest build of this product (v2019.1 build 0681) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.