4

In a dummy WinForms app, I'm able to create a ListBox at design time, create a background thread at runtime, and then add controls to the ListBox from the background thread. But if I did the same in WPF, I get an error.

Why am I am able to do this in WinForms, but not WPF? Is my WinForm example not the same as the WPF one? Or is there indeed a reason why it works just fine in WinForms and not WPF?

WinForms:

private List<Label> _labels;

public Form1()
{
    InitializeComponent();

    Thread test = new Thread(DoStuff);
    test.SetApartmentState(ApartmentState.STA);
    test.Start();
}

private void DoStuff()
{
    _labels = new List<Label>();

    _labels.Add(new Label() { Text = "Label1" });
    _labels.Add(new Label() { Text = "Label2" });
    _labels.Add(new Label() { Text = "Label3" });

    if (listBox1.InvokeRequired)
    {
        listBox1.Invoke((MethodInvoker)delegate { listBox1.DataSource = _labels; });
    }
    else
    {
        listBox1.DataSource = _labels;
    }
}

WPF:

public partial class MainWindow : Window
{
    private ObservableCollection<Label> _labels;
    public MainWindow()
    {
        InitializeComponent();

        Thread test = new Thread(DoStuff);
        test.SetApartmentState(ApartmentState.STA);
        test.Start();
    }

    private void DoStuff()
    {
        _labels = new ObservableCollection<Label>();
        _labels.Add(new Label() { Content = "Label1" });
        _labels.Add(new Label() { Content = "Label2" });
        _labels.Add(new Label() { Content = "Label3" });

        this.Dispatcher.BeginInvoke((Action)(() =>{ icMain.ItemsSource = _labels; }));
    }
}

This is the error I receive. Pretty standard and expected:

enter image description here

ernest
  • 1,633
  • 2
  • 30
  • 48
  • 3
    You're really not even supposed to do that in Winforms. It's a specific design of WPF, you have to modify a UI element from the thread that owns it. To modify it from another thread, you need to use a Dispatcher – cost Dec 01 '14 at 21:34
  • The WinForms example isn't doing anything relevant from a background thread. `listBox1.Invoke(...)` ensures that the passed delegate is invoked on the UI thread. –  Dec 01 '14 at 21:40
  • @hvd I think his question is that _labels is created on the background thread in both examples, so it should cause the same error when its used on the UI thread? – Allan Elder Dec 01 '14 at 21:51
  • @AllanElder `_labels` is not a user interface element and neither `List` nor `ObservableCollection` have such cross-thread checks (except perhaps for concurrent accesses), so it would surprise me if that caused any confusion, but you may nevertheless be right about it. –  Dec 01 '14 at 21:53

2 Answers2

2

WinForms isn't as strict about checking for cross-threading issues. This is probably because WinForms doesn't actually have controls such as labels. Rather, they are just wrappers around the real controls that are implemented at the OS level.

Since this is an implementation detail, there is no guarantee that your WinForms code will continue to work in the future. (That said, it isn't under active development so it will probably continue working.)

Jonathan Allen
  • 68,373
  • 70
  • 259
  • 447
  • I cannot speak for the WPF example, but for the WinForms example in the question, there *isn't* any cross-threading issue. The control is *only* accessed from the UI thread. –  Dec 02 '14 at 10:22
  • The three Labels being created are controls. – Jonathan Allen Dec 02 '14 at 17:15
  • Ah! It seemed to me that the `Label` in the question was not a control, but if it is, that would explain everything. In that case, what the OP is doing just doesn't make sense. There is no reason why a list box's data source or item source should contain any controls. –  Dec 02 '14 at 18:36
-2

I suppose that adding childs, or ever modifying properties of controls is bad practice at WinForms too. WPF just has more advanced ways to control access to UI from another threads, and warns you in early stage.

Related question.

Community
  • 1
  • 1
Nipheris
  • 487
  • 5
  • 12