【问题标题】:TaskFactory New UI CreationTaskFactory 新 UI 创建
【发布时间】:2014-04-25 22:16:46
【问题描述】:

如何使用 TaskFactory 创建新的 UI 元素?当我尝试时,我收到以下错误:

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

示例代码

Dim txtBoxList as new List(Of TextBox)

Sub StartThread()
    Dim TS As TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
    Task.Factory.StartNew(Sub() CreateControl(), TS)
End Sub

Sub CreateControl()
    Dim txtBox As New TextBox
    Dispatcher.BeginInvoke(Sub() txtBoxList.Add(txtBox))
End Sub

【问题讨论】:

  • 是的,你可以列出几乎任何东西,包括文本框去试试Dim a as new List(of TextBox)
  • OK 让我们运行。假设我将凭据保存在这样的地方,每个用户都将显示为自定义控件(它看起来像一个磁贴)我如何从 XAML 动态创建控件?
  • (我第三次这么说;) - 使用ItemsControl

标签: wpf vb.net taskfactory


【解决方案1】:

如果您正在使用 WPF,您确实需要抛开您可能从古老技术中学到的任何和所有概念,并理解并接受 The WPF Mentality

基本上,您几乎从不需要在 WPF 的过程代码中创建或操作 UI 元素。相反,WPF 适合大量使用DataBinding

The WPF Threading Model不允许允许您在后台线程中创建或操作 UI 元素的实例,并将它们添加到由“主”UI 线程创建的 Visual Tree

无论如何,这种事情的需求几乎为零,因为在大多数情况下,创建 UI 元素是一项可以(并且必须)由 UI 线程执行的微不足道的任务。

与其担心可视化树,您应该专注于在后台线程中加载您的数据,然后将其作为DataContext 传递给 UI,以便它可以相应地显示您的数据。

这是一个小例子,它使用ItemsControl 显示用户列表,这些用户在后台线程中异步加载,然后分派到 UI 线程进行显示:

<Window x:Class="WpfApplication7.AsyncItemsControl"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border Background="LightGray" BorderBrush="Black" BorderThickness="1" Margin="2">
                    <StackPanel>
                        <TextBlock Text="{Binding LastName}" Margin="2"/>
                        <TextBlock Text="{Binding FirstName}" Margin="2"/>
                    </StackPanel>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

代码背后:

public partial class AsyncItemsControl : Window
{
    public AsyncItemsControl()
    {
        InitializeComponent();

        var dispatcher = TaskScheduler.FromCurrentSynchronizationContext();

        Task.Factory.StartNew(() => GetUsers())
                    .ContinueWith(x => DataContext = x.Result,dispatcher);

    }

    public List<User> GetUsers()
    {
        // pretend this method calls a Web Service or Database to retrieve the data, and it takes 5 seconds to get a response:
        Thread.Sleep(5000);

        return new List<User>
        {
            new User() {FirstName = "Marty", LastName = "McFly"},
            new User() {FirstName = "Emmett", LastName = "Brown"},
            new User() {FirstName = "Bufford", LastName = "Tannen"}
        };
    }
}

数据项:

public class User
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

结果:

  • 请注意,该示例使用 DataBinding,它不会在过程代码中创建或操作 UI 元素,而是使用具有简单 string 属性的简单 User 类进行操作。

  • 还要注意,在 5 秒的“加载”时间内,UI 是响应式的,因为实际工作是由后台线程执行的。

  • 这种方法可以在 UI 和数据之间实现更大的分离,从而在无需更改底层业务/应用程序逻辑的情况下实现更大的 UI 可扩展性和可定制性。

  • 注意ItemsControl 如何创建和呈现显示 3 个数据项所需的适当 UI 元素。 “每个项目的外观”的实际定义是DataTemplate

  • 我强烈建议阅读本答案中链接的材料,以更深入地了解 WPF 的一般工作原理。

  • 旁注:如果您的目标是 C# 5.0,您可以利用 async / await 并通过删除所有基于 Task 的内容来使此代码更清晰。我使用的是 C# 4.0,因此我无法使用该功能。

  • WPF Rocks。只需将我的代码复制并粘贴到 File -&gt; New Project -&gt; WPF Application 中,然后自己查看结果。

  • 如果您需要进一步的帮助,请告诉我。

【讨论】:

  • 仅供参考,async 在 .NET 4.0 上可用;只需安装Microsoft.Bcl.Async NuGet 包。我一直在 .NET 4 上使用 async WPF。
  • 关于这种方法的最后一个问题 对于每个图块,我想包含一个...按钮或文本框,然后引导一个命令让我添加到您提供的示例中 如果我想包含一个每个磁贴底部的按钮....用于删除该磁贴用户 1 磁贴:单击删除按钮:磁贴被删除。你怎么能这样?
  • HighCore,我为此悬赏 500 以感谢您花时间向新手解释整个“WPF 心态”。我知道这通常是一项吃力不讨好的任务,但我非常感谢它(我相信很多新手也会这样做!):)
  • @Rachel 谢谢! =)
  • 这个答案应该放在FAQ中。
猜你喜欢
  • 1970-01-01
  • 2020-05-31
  • 1970-01-01
  • 1970-01-01
  • 2015-11-22
  • 1970-01-01
  • 1970-01-01
  • 2021-10-25
  • 2021-10-27
相关资源
最近更新 更多