views:

1283

answers:

3

I have a WPF ListView that I am trying to filter within a BackgroundWorker. My code is shown below:

Dim Worker As New BackgroundWorker
AddHandler Worker.DoWork, AddressOf Me.FilterAsync
Me.TextBoxText = Me.TextBox.Text
Worker.RunWorkerAsync(Me.TextBox)

Private Sub FilterAsync(ByVal sender As Object, ByVal e As DoWorkEventArgs)
    '
    Dim BackgroundWorker As BackgroundWorker = CType(sender, BackgroundWorker)
    Dim Text As String = e.Argument.ToString
    '
    Dim ListView As ListCollectionView = CType(CollectionViewSource.GetDefaultView(Me.ListView.ItemsSource), ListCollectionView)
    If Text <> String.Empty Then
        ListView.Filter = New Predicate(Of Object)(AddressOf Me.FindItemsAsync)
    Else
        ListView.Filter = Nothing
    End If
    '
End Sub

This code runs through the filtering however it fails with an error "The calling thread cannot access this object because a different thread owns it." on the following line:

ListView.Filter = New Predicate(Of Object)(AddressOf Me.FindItemsAsync)

What would be the problem here? I can't seem to find any samples with filtering through the BackgroundWorker.

Update: Does anyone know of a sample that filters a WPF ListView using a BackgroundWorker?

A: 

A ListCollectionView that is bound to a control can only be accessed from the thread that created it. So you can only set the Filter property on the UI thread...

Thomas Levesque
Thanks! Okay, if I setup the Filter to be set on the UI thread then won't the predicate run on the UI thread as well and not within the BackgroundWorker thread?
Luke
Assigning the Filter triggers the evaluation of the predicate on each item, so yes, it will run on the UI thread. Actually I'm not sure it's possible to do the filtering in a background thread... at least, not easily.
Thomas Levesque
A: 

As Thomas points out in the comments, this approach is entirely bunk for WPF, it's the WinForms approach

Building on Thomas' answer, if a background thread needs to update the UI then it needs to first switch onto the thread which created the UI.

For the sake of simplicity you can think of a single "UI thread" and then "background threads". The UI thread is in charge of drawing things to the screen, handling user interaction, etc... Calls from the background thread that modify the UI can cause all sorts of messes, so starting in .NET 2.0 (or maybe 1.1) the default behavior is to throw an exception rather than allowing the potentially dangerous call to succeed.

Typically (at least in the 2.0 world) you do this by "Invoke'ing" the Form/Control you need to update, "Invoke" is a bit ambiguous in the framework, but in the context of UI controls it means "return to the UI thread".

In 2.0 the typical pattern used to do this would be along the lines of:

Private DelegateSub UpdateSomeUICaller()
Private Sub UpdateSomeUI()
    If Me.InvokeRequired Then
  Dim delg as new UpdateSomeUICaller(AddressOf UpdateSomeUI)
  Me.Invoke(delg)
     Exit Sub
 End If

 Me.SomeUiControl.Text = "Hello from the UI Thread!"

End Sub

ONE BIG WARNING: the "delg" object in the example also contains an "Invoke" member, which is NOT the Invoke method you're looking for, you need "Invoke(delg)", not "delg.Invoke()"--that's the ambiguity I mentioned

STW
Me.Invoke and Me.InvokeRequired are for Windows Forms, not WPF... In WPF you use the Dispatcher property to invoke methods on the UI thread.
Thomas Levesque
@Thomas seriously good to know, if you can't tell I've barely got my foot in the door for WPF (really more like I've barely started on XAML)...
STW
+1  A: 

I guess running the filter is going to take a long time and so you want to run the filtering code in a background thread - this is just not possible.

Code that talks to the UI (including setting the filter and the code inside the filter) has to run in the UI thread.

What you can do is, inside the BackgroundWorker code, create a new list that includes only the items that should be filtered in and then, after the BackgroundWorker finishes and you are are back in the UI thread, set the new list to the ListView's ItemSource.

Nir