UI的并行生成 [英] Parallel Generation of UI

查看:380
本文介绍了UI的并行生成的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们拥有一个具有与高速缓存的VirtualizingStackPanel一个ListBox WPF应用程序。不是因为它有大量的许多元素(在极端情况下,通常低于20,但也许多达100个或更多),而是因为元素需要时间来产生。元素是实际上的UIElement对象。因此,应用程序动态需要生成UI元素。

We have a WPF application that has a ListBox with a VirtualizingStackPanel with caching. Not because it has massively many elements (typically less than 20 but perhaps up to 100 or more in extreme cases) but because elements take time to generate. The elements are in fact UIElement objects. So the application dynamically needs to generate UIElements.

现在的问题是,尽管虚拟化似乎工作,应用程序仍然慢慢的响应,这是一个以最小的噪声的概念解决方案的论证。

The problem is that even though the virtualization appears to work, the application is still slow to become responsive, and this is in a proof of concept solution with minimal "noise".

因此,我们计算过,因为主要的问题是,我们产生复杂的UIElement动态物体,我们需要做的并行,即关闭线程。但是,我们得到的代码需要在一个STA线程上运行一个错误:

So we figured that since the main problem is that we generate complex UIElement objects dynamically, we need to do that in parallel, i.e. off-thread. But we get an error that the code needs to be run on a STA thread:

调用线程必须STA,因为许多UI组件需要这一点。

The calling thread must be STA, because many UI components require this.

这是否意味着我们不能在除WPF主UI线程其他线程生成UI(的UIElement对象)?

Does this mean that we cannot generate UI (UIElement objects) on thread other than the WPF main UI thread?

这是我们从概念解决方案的论证相关的代码片段:

Here's a relevant code fragment from our proof of concept solution:

public class Person : ObservableBase
{
    // ...

    UIElement _UI;
    public UIElement UI
    {
        get
        {
            if (_UI == null)
            {
                ParallelGenerateUI();
            }
            return _UI;
        }
    }

    private void ParallelGenerateUI()
    {
        var scheduler = TaskScheduler.FromCurrentSynchronizationContext();

        Task.Factory.StartNew(() => GenerateUI())
        .ContinueWith(t =>
        {
            _UI = t.Result;
            RaisePropertyChanged("UI");
        }, scheduler);
    }

    private UIElement GenerateUI()
    {
        var tb = new TextBlock();
        tb.Width = 800.0;
        tb.TextWrapping = TextWrapping.Wrap;
        var n = rnd.Next(10, 5000);
        for (int i = 0; i < n; i++)
        {
            tb.Inlines.Add(new Run("A line of text. "));
        }
        return tb;
    }

    // ...
}

和这里是一个相关的一块XAML的:

and here is a relevant piece of XAML:

<DataTemplate x:Key="PersonDataTemplate" DataType="{x:Type local:Person}">
    <Grid>
        <Border Margin="4" BorderBrush="Black" BorderThickness="1" MinHeight="40" CornerRadius="3" Padding="3">

            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <!--<RowDefinition />-->
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <TextBlock Text="Name : " Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" />
                <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" />
                <TextBlock Text=" - Age : " Grid.Column="2" Grid.Row="0" FontWeight="Bold"
                        HorizontalAlignment="Right" />
                <TextBlock Grid.Column="3" Grid.Row="0" Text="{Binding Age}" />
                <ContentControl Grid.Column="4" Grid.Row="0" Content="{Binding Path=UI}" />

            </Grid>
        </Border>
    </Grid>
</DataTemplate>



正如你可以看到我们的数据绑定到类型的UIElement的属性UI。

As you can see we databind to a property UI of type UIElement.

<ListBox x:Name="listbox" ItemsSource="{Binding Persons}" Background="LightBlue"
    ItemTemplate="{StaticResource PersonDataTemplate}"
    ItemContainerStyle="{StaticResource ListBoxItemStyle}" 
    VirtualizingPanel.IsVirtualizing="True"
    VirtualizingPanel.IsVirtualizingWhenGrouping="True" 
    VirtualizingStackPanel.ScrollUnit="Pixel"  
    VirtualizingStackPanel.CacheLength="10,10"
    VirtualizingStackPanel.CacheLengthUnit="Item"
>
    <ListBox.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
    </ListBox.GroupStyle>

</ListBox>

在关闭情况下,正是我们的应用程序所做的是创建一个代码视图,其中列表是程序,再次包含的结构化内容的混合(为参数和一方面局部变量和语句和表达式为另一方。)

In closing context, what our application does is create a code view where the list is of procedures which again contain a mix of structured content (for parameters and local variables on one hand and statements and expressions on the other.)

在换句话说我们的UIElement对象过于复杂创建只通过数据绑定。

In other words our UIElement objects are too complex to create via databinding alone.

我们有另外的想法是使用了XAML异步的设置,因为它似乎可能创造非阻塞UI,但我们一直没能够实现这一点,因为我们得到了相同的错误如上:

Another thought we had was to use "Async" settings in the XAML as it appears possible to create "non-blocking UI" but we have not been able to implement this because we get the same error as above:

调用线程必须STA,因为许多UI组件都需要这个

The calling thread must be STA, because many UI components require this.

堆栈跟踪:

System.InvalidOperationException was unhandled by user code
  HResult=-2146233079
  Message=The calling thread must be STA, because many UI components require this.
  Source=PresentationCore
  StackTrace:
       at System.Windows.Input.InputManager..ctor()
       at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
       at System.Windows.Input.KeyboardNavigation..ctor()
       at System.Windows.FrameworkElement.FrameworkServices..ctor()
       at System.Windows.FrameworkElement.EnsureFrameworkServices()
       at System.Windows.FrameworkElement..ctor()
       at System.Windows.Controls.TextBlock..ctor()
       at WPF4._5_VirtualizingStackPanelNewFeatures.Person.GenerateUI() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 84
       at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 68
       at System.Threading.Tasks.Task`1.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException: 

编辑:

1)增加了更多的XAML。
2)增加堆栈跟踪。

1) Added more XAML. 2) Added stacktrace.

推荐答案

我很痛苦在正常的C#环境相同的问题。我也尝试过很多事情。你计算控件的大小来调整父的大小预先?我很遗憾这样做。

I am suffering the same problem in normal c# environment. I also tried lots of things. Do you calculate the size of controls to adjust the size of the parent in advance? I am doing this unfortunately.

您也可以创建一个动态的控制筑巢你的孩子。通过您可以创建类的一个的UIElement适配器。该适配器在启动时创建的,并拥有所有信息来创建UI元素。
适配器可以创建要求孩子们在STA线程只是在时间的需求。当向上或向下滚动,你可以事先在您滚动的方向打造的儿童。这样,您就可以启动例如5-10 UI元素,然后你滚动更多的计算。

You may also create a control nesting your children dynamically. By that you can create kind of an UIElement Adapter. The adapter is created at the start time and has all information to create the UIElements. The adapter could create requested children on STA thread on demand just in time. When scrolling up or down you may create children in advance in the direction you are scrolling. This way you can start with e.g. 5-10 UI elements and then you calculate by scrolling up more.

我知道这是不是很好,这将是更好的,如果有一些技术框架内提供这样的事情,但我没有发现它。

I know this is not so nice and it would be better, if there is some technology within the framework providing something like this, but I did not found it yet.

您还可以看看那些两件事情。人帮我很多在响应控制。另外仍然是开放的,因为你需要的.NET Framework 4.5:

You may look also at those two things. One helped me much in control responsive. The other is still open, since you need .NET Framework 4.5:


  1. SuspendLayout ResumeLayout 不工作非常好。你可以试试这个:

  1. SuspendLayout and ResumeLayout don't operate very nice. You may try this:

/// <summary>
/// An application sends the WM_SETREDRAW message to a window to allow changes in that 
/// window to be redrawn or to prevent changes in that window from being redrawn.
/// </summary>
private const int WM_SETREDRAW = 11; 

/// <summary>
/// Suspends painting for the target control. Do NOT forget to call EndControlUpdate!!!
/// </summary>
/// <param name="control">visual control</param>
public static void BeginControlUpdate(Control control)
{
    Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
          IntPtr.Zero);

    NativeWindow window = NativeWindow.FromHandle(control.Handle);
    window.DefWndProc(ref msgSuspendUpdate);
}

/// <summary>
/// Resumes painting for the target control. Intended to be called following a call to BeginControlUpdate()
/// </summary>
/// <param name="control">visual control</param>
public static void EndControlUpdate(Control control)
{
    // Create a C "true" boolean as an IntPtr
    IntPtr wparam = new IntPtr(1);
    Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
          IntPtr.Zero);

    NativeWindow window = NativeWindow.FromHandle(control.Handle);
    window.DefWndProc(ref msgResumeUpdate);
    control.Invalidate();
    control.Refresh();
}


  • Dispatcher.Yield

    这篇关于UI的并行生成的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆