TaskFactory New UI Creation

2019-01-07 00:17发布

问题:

How do I create a new UI element using TaskFactory? When I try I get the following error :

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

Example Code

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

回答1:

If you're working with WPF, you really need to leave behind any and all notions you might have learned from ancient technologies and understand and embrace The WPF Mentality.

Basically, you almost never need to create or manipulate UI elements in procedural code in WPF. Instead, WPF lends itself to heavily use DataBinding.

The WPF Threading Model does not allow you to create or manipulate instances of UI elements in background threads, and add them to the Visual Tree which was created by the "main" UI thread.

Anyways, there is almost zero need for such a thing, because creating UI elements is in most cases a trivial task which can (and must) be performed by the UI Thread.

rather than worrying about the Visual Tree, you should concentrate on having your Data loaded in background threads, and then passed as a DataContext to the UI so that it can display your data accordingly.

This is a small example which uses an ItemsControl to display a list of users, which are loaded asynchronously in a background thread and then dispatched to the UI thread for display:

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

Code Behind:

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"}
        };
    }
}

Data Item:

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

Result:

  • Notice that the example uses DataBinding and it does not create or manipulate UI elements in procedural code, but rather operates with a simple User class with simple string properties.

  • Also notice that during the 5 seconds "load" time, the UI is responsive because the actual work is being performed by a background thread.

  • This approach allows a greater separation between UI and data which allows a much greater scalability and customizability of the UI without having to change the underlying business / application logic.

  • Notice how the ItemsControl takes care of creating and rendering the appropiate UI elements needed to display the 3 Data Items. The actual definition of "how each item looks" is the DataTemplate.

  • I strongly recommend reading the material linked thoughout this answer for more in-depth understanding of how WPF works in general.

  • Side note: if you're targetting C# 5.0 you can leverage async / await and make this code cleaner by removing all the Task based stuff. I'm on C# 4.0 so that feature is not available for me.

  • WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.

  • Let me know if you need further help.