wpfvb.nettaskfactory

TaskFactory New UI Creation


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

Solution

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

    enter image description here