I have a ComboBox
, which its ItemsSource
is set to an object which inherits ObservableCollection
.
The object gets refreshed with new data on a timer.
Since sometimes there is a large set of new data, I don't use the Add
method on the ObservableCollection
, but rather I use the following code:
For Each itm In MyNewItems
Items.Add(itm)
Next
MyBase.OnPropertyChanged(New PropertyChangedEventArgs("Count"))
OnPropertyChanged(New PropertyChangedEventArgs("Items[]"))
'NEXT LINE CAUSES ISSUE
OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
The problem is that when the last line runs, the Text
of the ComboBox
gets reset to an empty string.
If I remove that line, then the issue is resolved, but the Items show old data, since the ComboBox
doesn't know that new data came in
Please advise
With appreciation
UPDATE
Hi, as requested, I'm posting the relevant code here
1: The Xaml, Pretty Simple:
<Window x:Class="dlgTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mch="clr-namespace:Machshevet.Windows;assembly=Machshevet" >
<StackPanel>
<TextBlock Text="{Binding CaseID}"/>
<mch:TestPick Name="cmbTest" SelectedValuePath="ID" DisplayMemberPath="Name" SelectedValue="{Binding CaseID}" IsEditable="True" IsTextSearchEnabled="False" />
</StackPanel>
</Window>
2: The TestPick class, not too complex either:
Public Class TestPick
Inherits ComboBox
Dim usertyped As Boolean
Function Query() As IQueryable
Dim txt = ""
Dispatcher.Invoke(Sub() txt = Text)
Dim ret = GetSlimContext.Query("viwCase").Select("new (ID,Name,ClientName,SubjectName)")
If txt <> "" AndAlso usertyped Then ret = ret.TextFiltered(txt)
Return ret
End Function
Private Sub EntityPick_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim qs = New QuerySource(Function() Query())
Me.ItemsSource = qs
qs.Control = Me
qs.ShouldRefresh = Function() True
End Sub
Private Sub EntityPick_PreviewTextInput(sender As Object, e As TextCompositionEventArgs) Handles Me.PreviewTextInput
usertyped = True
End Sub
Private Sub TestPick_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles Me.SelectionChanged
If e.AddedItems.None Then
Dim a = 1
End If
End Sub
End Class
3: The QuerySource class which does all the heavy lifting
Public Class QuerySource
Inherits ObjectModel.ObservableCollection(Of Object)
Event Refreshed(sender As QuerySource, e As EventArgs)
Property RefreshSpan As TimeSpan = TimeSpan.FromSeconds(3)
Property CheckProperties As Boolean = True
Property Control As ItemsControl
Dim Timer As Threading.Timer = New Threading.Timer(Sub() TimerTick(), Nothing, 0, 600)
Dim _lastRefresh As Date?
Dim Query As Func(Of IQueryable)
Dim workingon As Date?
Sub New(Query As Func(Of IQueryable), Control As ItemsControl)
Me.Control = Control
Me.Query = Query
End Sub
Async Sub TimerTick()
Try
If Now - _lastRefresh.GetValueOrDefault < RefreshSpan Then Exit Sub
If GetLastInputTime() > 60 * 15 Then Exit Sub
Dim isvis = False
Await Control.Dispatcher.BeginInvoke(Sub() isvis = Control.IsUserVisible())
If Not isvis Then Exit Sub
If workingon.HasValue AndAlso workingon.Value > Now.AddSeconds(-15) Then Exit Sub 'if wasnt working for 15 seconds, probaly err or something
workingon = Now
Dim fq = Query.Invoke
Dim itmtype = fq.ElementType
Dim props = itmtype.CachedProperties.Where(Function(x) x.CanWrite AndAlso x.IsScalar(True)).ToList
Dim keyprops = itmtype.CachedKeyProperties.ToList
Dim newData = fq.ToObjectList
If newData Is Nothing Then Exit Sub
Dim keySelector As Func(Of Object, Object)
Dim diff As CollectionDiff(Of Object)
If itmtype.IsScalar Then 'list of strings..
keySelector = Function(x) x
Else
If keyprops.Count <> 1 Then DevError("?")
Dim kp = keyprops.FirstOrDefault
keySelector = Function(x) kp.GetValue(x)
End If
diff = CollectionDiff(Me, newData, keySelector, props, CheckProperties)
Dim toPreserve As Object
ExecIfType(Of Primitives.Selector)(Control, Sub(x) toPreserve = x.Dispatcher.Invoke(Function() x.SelectedItem))
If toPreserve IsNot Nothing Then diff.ToPreserve = {toPreserve}.ToDictionary(Function(x) x, Function(x) Nothing)
diff.PreserveOnDelete = True
If diff.ModificationCount > 400 Or diff.ClearOld Then
CheckReentrancy()
If diff.ClearOld Then
Items.Clear()
Else
For Each pair In diff.ToReplaceByIndex
Control.Dispatcher.Invoke(Sub() Items(pair.Key) = pair.Value)
Next
For Each idx In diff.GetIndexesToDelete
Items.RemoveAt(idx)
Next
End If
For Each itm In diff.ToAdd 'for mem optimization im not using addrange
Items.Add(itm)
Next
MyBase.OnPropertyChanged(New PropertyChangedEventArgs("Count"))
OnPropertyChanged(New PropertyChangedEventArgs("Items[]"))
Control.Dispatcher.Invoke(Sub() OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)))
Else
Dim preservIdx = diff.ToPreserve?.Select(Function(x) Items.IndexOf(x.Key))?.ToHashSet
For Each pair In diff.ToReplaceByIndex
Control.Dispatcher.Invoke(Sub() Me(pair.Key) = pair.Value)
Next
For Each idx In diff.GetIndexesToDelete
If diff.PreserveOnDelete AndAlso preservIdx IsNot Nothing AndAlso preservIdx.Contains(idx) Then Continue For
Control.Dispatcher.Invoke(Sub() RemoveAt(idx))
Next
'don't use addrange - will cause a reset
Await Control.Dispatcher.BeginInvoke(Sub() diff.ToAdd.ForEach(Sub(x) Add(x)))
End If
_lastRefresh = Now
workingon = Nothing
Control.Dispatcher.Invoke(Sub()
Dim cvs = System.Windows.Data.CollectionViewSource.GetDefaultView(Me)
If cvs.SortDescriptions.None Then
Dim defsorts = {KVP("Name", False), KVP(NameOf(RecordBase.LastEditedOn), True), KVP(NameOf(LiteRecordBase.ID), True)}
For Each defsort In defsorts
If itmtype.HasProperty(defsort.Key) Then
cvs.SortDescriptions.Add(New SortDescription(defsort.Key, If(defsort.Value, ListSortDirection.Descending, ListSortDirection.Ascending)))
Exit For
End If
Next
End If
End Sub)
RaiseEvent Refreshed(Me, Nothing)
Catch ex As Exception
Control.Dispatcher.BeginInvoke(Sub() ex.Rethrow)
End Try
End Sub
End Class