2

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
svick
  • 236,525
  • 50
  • 385
  • 514
ArcSet
  • 6,518
  • 1
  • 20
  • 34
  • Yes you can make a list of almost anything including textboxes Go try it out `Dim a as new List(of TextBox)` – ArcSet Apr 25 '14 at 22:28
  • OK lets run with that. So say I am saving credentials in such away that each user will show up as a Custom Control (it looks like a tile) How can i create controls on the fly from XAML? – ArcSet Apr 25 '14 at 22:32
  • (third time I'm saying this ;) - Use an [ItemsControl](http://drwpf.com/blog/itemscontrol-a-to-z/). – Federico Berasategui Apr 25 '14 at 22:33

1 Answers1

9

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

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

Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • 3
    FYI, `async` is available on .NET 4.0; just install the `Microsoft.Bcl.Async` NuGet package. I use `async` WPF on .NET 4 all the time. – Stephen Cleary Apr 25 '14 at 23:37
  • One Last Question about this approach With each tile i want to include a ...button or textbox that then leads to a command Let me just add to the example you gave What if i wanted to include a Button at the bottom of each of those tiles....for removing that tile User 1 tile: Click remove button: Tile is removed. How could you do that? – ArcSet Apr 28 '14 at 16:55
  • @ArcSet See [DelegateCommand](http://wpftutorial.net/DelegateCommand.html). Anyways that deserves a separate question on it's own, don't you think? ;) – Federico Berasategui Apr 28 '14 at 16:58
  • 5
    HighCore, I put a 500 bounty on this to say thank you for all the extra time you take to explain the whole "WPF Mentality" to newbies. I know it's often a thankless and time-consuming task, but I do appreciate it a lot (and I'm sure many newbies do too!) :) – Rachel Jun 03 '14 at 20:27
  • 1
    This answer should be put in the FAQ. – Dour High Arch Jun 18 '14 at 16:29
  • "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." does it actually say so anywhere in the documentation ? Why would they not allow such behavior ? I mean you may have a requirement e.g to create a checker board n x n of any size choosen by user e.g 5 x 5, 10 x1 0, 20 x 20 it makes sense to create a grid with 400 cells on separate thread and add it to the view tree, it may take like 2 or more seconds to create it. – IronHide Feb 18 '18 at 21:39