简体   繁体   中英

DispatcherObject cast woes and Async / ObservableCollection issues in WPF

The code below pulls out a bunch of records from an Access 2010 database; hence rolling my own connector bits. I've succeeded in doing the observablecollection and made it all bind up with nice drag and drop data sources, from my own objects. However, like a daft person, I want to do this Asynchronously. Yet, I've got a small cast monster problem, and I don't know what to feed it! Can anyone advise me - I've tried a lot of reading around, but the concepts are just a little too many at once on a Friday afternoon and I'm struggling to make any real headway.

The line I'm having trouble with is: Dim dispatcherObject As DispatcherObject = CType (handler.Target, DispatcherObject )

The exception is: Unable to cast object of type '_Closure$__2[SomeRecord_Viewer.SomeRecord]' to type 'System.Windows.Threading.DispatcherObject'.

I've managed to make a WPF listbox populate via the code below, however only by commenting out a part of the ObservableCollectionEx class. This causes synchronisation problems and a crash after a few hundred records are entered.

Class that builds the threaded list of entities - in this case, an ObservableCollectionEx(Of SomeRecord):

Class SomeRecordSet
Inherits ObservableCollectionEx( Of  SomeRecord)

Private Shared Property _SomeRecordList As New ObservableCollectionEx(Of  SomeRecord )
Public Shared ReadOnly Property SomeRecordList As ObservableCollectionEx(Of  SomeRecord )
    Get
        If _SomeRecordList.Count = 0 Then BuildSomeRecordListAsync()
        Return _SomeRecordList
    End Get
End Property

Public Shared ReadOnly Property ReturnSingleSomeRecord(id As Integer) As SomeRecord
    Get
        Return ( From SomeRecord In _SomeRecordList Where SomeRecord.id = id Select        SomeRecord).First()
    End Get
End Property

Private Shared Async Sub BuildSomeRecordListAsync()
    Await Task.Run( Sub() BuildSomeRecordList())
    Return
End Sub

Private Shared Sub BuildSomeRecordList()
    Db.newcmd( "Select * from  RecordList ")
    While Db.read
        Dim SomeRecord As New SomeRecord
        With SomeRecord
            .id = Db.dbint( "ID")
            .type = Db.dbin( "type")
         End With
        _SomeRecordList.Add(SomeRecord)
    End While
End Sub`

Partial code for the SomeRecord class:

Class SomeRecord
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements                   INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged( ByVal info As String)
    RaiseEvent PropertyChanged(Me , New PropertyChangedEventArgs (info))
End Sub

...'lots of simple properties.
End Class

The threaded collection class code - translated from another online source.

'I use PostSharp for try catch stuff. ` Public Class ObservableCollectionEx (Of T ) Inherits ObservableCollection( Of T) ' Override the event so this class can access it Public Shadows Event CollectionChanged As System.Collections.Specialized.NotifyCollectionChangedEventHandler

Protected Overrides Sub OnCollectionChanged( ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs )

    Using BlockReentrancy()

        Dim eventHandler As System.Collections.Specialized.NotifyCollectionChangedEventHandler = Sub () RaiseEvent CollectionChanged(Me , e)
        If (eventHandler Is Nothing) Then Return

        Dim delegates() As [Delegate] = eventHandler.GetInvocationList
*******If I comment this out I can populate the Listbox via a CollectionView, however it dies with issues to do with the list not staying synchronised :).
        'Walk thru invocation list 
        For Each handler As System.Collections.Specialized.NotifyCollectionChangedEventHandler     In delegates
            Dim dispatcherObject As DispatcherObject = CType (handler.Target, DispatcherObject)
            ' If the subscriber is a DispatcherObject and different thread
            If (( Not (dispatcherObject) Is Nothing) AndAlso (dispatcherObject.CheckAccess = False )) Then
                ' Invoke handler in the target dispatcher's thread
                dispatcherObject.Dispatcher.Invoke(DispatcherPriority .DataBind, handler, Me, e)
            Else
                handler( Me, e)
            End If
        Next

*******End of stuff I comment out to get working partially***

    End Using
End Sub
End Class

From what I can see, you have two problems.

  1. You're assigning the local variable eventHandler to an anonymous method, rather than the actual event handler. It should be:

     Dim eventHandler As NotifyCollectionChangedEventHandler = CollectionChangedEvent 

    NB: You need to use CollectionChangedEvent in VB, not CollectionChanged .

  2. You're using CType to cast the target to a DispatcherObject , which won't work if the target isn't a DispatcherObject . Use TryCast instead:

     Dim dispatcherObject As DispatcherObject = TryCast(handler.Target, DispatcherObject) 

You can also tidy up the test on the next line by using IsNot :

If dispatcherObject IsNot Nothing AndAlso Not dispatcherObject.CheckAccess Then

WARNING - The code below acts differently to the C# version. The key difference seems to be that in VB you can't Override an Event (Why on earth not?) yet in C# you can.

The result is the Handler is Nothing in VB but not in C# :(.

So the syntax builds without error but the VB version doesn't ever do anything.

Redone with the updated answer in VB. Thank you!

Note I cannot make this work with Entity Framework, yet. But I think that a me and EF issue, not the collection.

The code itself is here for anyone interested. My list DOES populate perfectly fine now. However, I would take this answer of mine with a small pinch of salt until I update saying how I've extensively tested perhaps :)

However the omens are good - here is the original C# author's site: Original Site

Public Class ObservableCollectionEx(Of T)
Inherits ObservableCollection(Of T)



'Override the event so this class can access it
Public Shadows Event CollectionChanged As NotifyCollectionChangedEventHandler

Protected Overrides Sub OnCollectionChanged(ByVal e As NotifyCollectionChangedEventArgs)
    Using BlockReentrancy()
        Dim eventHandler As System.Collections.Specialized.NotifyCollectionChangedEventHandler = CollectionChangedEvent
        If eventHandler Is Nothing Then
            Return
        End If


        Dim delegates() As [Delegate] = CollectionChangedEvent.GetInvocationList
        'Walk thru invocation list
        For Each handler As NotifyCollectionChangedEventHandler In delegates
            Dim dispatcherObject As DispatcherObject = TryCast(handler.Target, DispatcherObject)
            ' If the subscriber is a DispatcherObject and different thread
            If dispatcherObject IsNot Nothing AndAlso Not dispatcherObject.CheckAccess Then
                ' Invoke handler in the target dispatcher's thread
                dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, Me, e)
            Else
                handler(Me, e)
            End If
        Next
    End Using
End Sub

End Class

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM