Reputation: 7279
I have a datagrid bound to an observable collection. Each item inside of the grid can have multiple detail lines, which are stored in an observable collection property inside of the main object.
The line details are being fetched from a linked server with a slow connection, so I'd like to put a background worker in to update the line details OC, but I get an error saying that the control can't be updated outside of the thread that made it.
What's the best way to do this. The 2 second lag is a bit much.
datagrid xaml:
<DataGrid AutoGenerateColumns="False" Name="dgROList" ItemsSource="{Binding ElementName=MainWindow, Path=cROInfo}" CanUserDeleteRows="True" CanUserReorderColumns="False" GridLinesVisibility="Horizontal" Margin="0,112,0,0" Grid.ColumnSpan="2" AlternatingRowBackground="#FFFFE776">
<DataGrid.Columns>
<DataGridTextColumn Header="RO Number" Width="Auto" Binding="{Binding RONum}" />
<DataGridTemplateColumn x:Name="roDetails" Header="RO Details" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl Name="LineDetails" ItemsSource="{Binding LineInfo}" Width="Auto">
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter />
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Line}" />
<Label Content="{Binding Status}" />
<Label Content="{Binding PaidAmount}" />
<Label Content="{Binding SDate}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--<DataGridTextColumn Header="RO Details" Width="*" Binding="{Binding RODetails}" />-->
</DataGrid.Columns>
</DataGrid>
OC class:
Imports System.ComponentModel
Imports System.Collections.ObjectModel
Public Class ocROInformation
Implements INotifyPropertyChanged
Private _RONum As String
Private _LineInfo As ObservableCollection(Of ocROLineInformation)
Private _Changed As Boolean
Private _RONumChanged As Boolean
Private _RODetailsChanged As Boolean
Private WithEvents bgworker As BackgroundWorker
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub New(ronum As String)
_RONum = ronum
_LineInfo = New ObservableCollection(Of ocROLineInformation)
bgworker = New BackgroundWorker
bgworker.RunWorkerAsync()
' GetData() ' If I call this directly it works, just lags out while the query runs for a little bit.
End Sub
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Sub GetData() Handles bgworker.DoWork
Dim DCodes As String = DealerCodes
Dim rSelect As New ADODB.Recordset
Dim sSql As String = "SELECT DISTINCT * FROM IGlobal WHERE RMAJBC = " & _RONum"
Dim line As Integer
Dim status As String = ""
Dim sdate As Date
Dim paidamount As Double
Dim tdate As String
With rSelect
.Open(sSql, MyCn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockReadOnly)
If .EOF Then
status = "Never Received"
End If
Do While Not .EOF
line = .Fields!LineNum.Value
status = NZ(.Fields!Stat6.Value, "")
paidamount = NZ(.Fields!PaidAmount.Value, 0)
tdate = NZ(.Fields!SDate.Value, "")
If Not tdate = "" And Not tdate = "0" Then
sdate = Date.ParseExact(tdate, "yyyyMMdd", System.Globalization.DateTimeFormatInfo.InvariantInfo)
_LineInfo.Add(New ocROLineInformation(line, status, sdate, paidamount))
Else
_LineInfo.Add(New ocROLineInformation(line, status))
End If
.MoveNext()
Loop
.Close()
End With
OnPropertyChanged("RODetails")
End Sub
Public Property Changed() As Boolean
Get
Return _Changed
End Get
Set(ByVal value As Boolean)
If _Changed <> value Then
_Changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Public Property RONum() As String
Get
Return _RONum
End Get
Set(value As String)
If _RONum <> value Then
_RONum = value
RONumChanged = True
OnPropertyChanged("RONum")
GetData()
End If
End Set
End Property
Public ReadOnly Property RODetails As String
Get
Dim output As String = ""
For Each l As ocROLineInformation In _LineInfo
output &= l.Print & " "
Next
Return output '"This is a test: " & _RONum
End Get
End Property
Public ReadOnly Property LineInfo As ObservableCollection(Of ocROLineInformation)
Get
Return _LineInfo
End Get
End Property
Public Property RODetailsChanged As Boolean
Get
Return _RODetailsChanged
End Get
Set(value As Boolean)
If _RODetailsChanged <> value Then
_RODetailsChanged = value
OnPropertyChanged("RODetailsChanged")
End If
End Set
End Property
Public Property RONumChanged() As Boolean
Get
Return _RONumChanged
End Get
Set(value As Boolean)
If _RONumChanged <> value Then
_RONumChanged = value
OnPropertyChanged("RONumChanged")
End If
End Set
End Property
End Class
Public Class ocROLineInformation
Implements INotifyPropertyChanged
Private _Line As Integer
Private _Status As String
Private _SDate As Date
Private _PaidAmount As Double
Private _Changed As Boolean
Private _LineChanged As Boolean
Private _StatusChanged As Boolean
Private _SDateChanged As Boolean
Private _PaidAmountChanged As Boolean
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Public Sub New(line As Integer, status As String, sdate As Date, paidamount As Double)
_Line = line
_Status = status
_SDate = sdate
_PaidAmount = paidamount
End Sub
Public Sub New(line As Integer, status As String)
_Line = line
_Status = status
End Sub
Public ReadOnly Property Print() As String
Get
Return "Line: " & _Line & ", Status: " & _Status & ", Amount: " & _PaidAmount & ", Date: " & _SDate.ToShortDateString
End Get
End Property
Protected Overridable Sub OnPropertyChanged(ByVal Propertyname As String)
If Not Propertyname.Contains("Changed") Then
Changed = True
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Propertyname))
End Sub
Public Property Changed() As Boolean
Get
Return _Changed
End Get
Set(ByVal value As Boolean)
If _Changed <> value Then
_Changed = value
OnPropertyChanged("Changed")
End If
End Set
End Property
Public Property Line() As Integer
Get
Return _Line
End Get
Set(value As Integer)
If _Line <> value Then
_Line = value
LineChanged = True
OnPropertyChanged("Line")
End If
End Set
End Property
Public Property Status() As String
Get
Return _Status
End Get
Set(value As String)
If _Status <> value Then
_Status = value
StatusChanged = True
OnPropertyChanged("Status")
End If
End Set
End Property
Public Property SDate() As Date
Get
Return _SDate
End Get
Set(value As Date)
If _SDate <> value Then
_SDate = value
SDateChanged = True
OnPropertyChanged("SDate")
End If
End Set
End Property
Private Property PaidAmount() As Double
Get
Return _PaidAmount
End Get
Set(value As Double)
If _PaidAmount <> value Then
_PaidAmount = value
PaidAmountChanged = True
OnPropertyChanged("PaidAmount")
End If
End Set
End Property
Public Property LineChanged() As Boolean
Get
Return _LineChanged
End Get
Set(value As Boolean)
If _LineChanged <> value Then
_LineChanged = value
OnPropertyChanged("LineChanged")
End If
End Set
End Property
Public Property StatusChanged() As Boolean
Get
Return _StatusChanged
End Get
Set(value As Boolean)
If _StatusChanged <> value Then
_StatusChanged = value
OnPropertyChanged("StatusChanged")
End If
End Set
End Property
Public Property SDateChanged() As Boolean
Get
Return _SDateChanged
End Get
Set(value As Boolean)
If _SDateChanged <> value Then
_SDateChanged = value
OnPropertyChanged("SDateChanged")
End If
End Set
End Property
Public Property PaidAmountChanged() As Boolean
Get
Return _PaidAmountChanged
End Get
Set(value As Boolean)
If _PaidAmountChanged <> value Then
_PaidAmountChanged = value
OnPropertyChanged("PaidAmountChanged")
End If
End Set
End Property
End Class
Upvotes: 0
Views: 1757
Reputation: 24713
You need to update the collection on the UI thread. To do this make sure to pass the data back via the Result
property on the DoWorkEventArgs
.
In your completed event you can then run through the data via the RunWorkerCompletedEventArgs.Result
property and set your ObservableCollection
accordingly, all on the UI thread.
If you wanted to avoid the BackgroundWorker
you could use the Dispatcher
.
ThreadStart start = delegate()
{
// make your calls to the db
Dispatcher.Invoke(DispatcherPriority.Normal,
new Action<object>(UpdateCollection),
new object[] { myData });
};
new Thread(start).Start();
private void UpdateCollection(object data)
{
//iterate your collection and add the data as needed
}
Whatever route you go, the root cause is attempting to access an object created on the UI thread from a differing thread.
Upvotes: 1