0

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
Yisroel M. Olewski
  • 1,560
  • 3
  • 25
  • 41
  • Could you share full code (XAML) or create concrete simple example where the issue occurs?I have tried make something (https://misaz.cz/Public/files/WpfApp4.zip) but it does not clear selected item. – Misaz Jul 21 '19 at 13:16
  • Are you sure that this is a specific problem with notifying `Reset`? How does it behave if you remove all items, then add all new items? Does the ComboBox not change its `Text` property? Or are you always only adding items and never delete old ones? – Clemens Jul 21 '19 at 15:04
  • its part of a huge code base, but i will try to minimize it as much as possible. any reason why there's a close request here? can i see who and why tried to close this? so i can try to improve this (and future) questions? thanks – Yisroel M. Olewski Jul 22 '19 at 07:58
  • @Misaz, Thanks. I've added full code. BTW, issue happens only when there's a `Binding`+`SelectedValue` – Yisroel M. Olewski Jul 23 '19 at 14:08
  • @Clemens, Hi. like i noted, without calling reset the text doesnt get cleared, which is fine. but the new items to show up in the collection. I'm adding and deleting, according to changes in the db. – Yisroel M. Olewski Jul 23 '19 at 14:10
  • Hi All. Any ideas? I cut out a lot of code from the QeerySource, which should make it somewhat simpler, though the issue still exists – Yisroel M. Olewski Jul 30 '19 at 12:57

1 Answers1

0

Okay

Thanks all for chipping in, in the end it seems like my answer is actually here

ObservableCollection : calling OnCollectionChanged with multiple new items

Works like a charm, and thank you all again for your time and patience

Yisroel M. Olewski
  • 1,560
  • 3
  • 25
  • 41