1
votes

I have a strange problem with a ListBox: I have bound it to a Collection that does not implement INotifyCollectionChanged (Not possible as it's items are dynamically generated on request):

IEnumerator IEnumerable.GetEnumerator()
{
    foreach (var metaData in Metadata.Where((m) => m.IsEnabled))
    {
        yield return new DynamicPropertyValue(this, metaData);
    }
}

Therefore, when IsVisible of the ListBox changes, I'm trying to force to update the ItemsSource by setting it to Null and then update the binding:

static void ItemsControl_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (true.Equals(e.NewValue))
    {
        var element = (ItemsControl)sender;
        var bindingExpression = BindingOperations.GetBindingExpression(element, ItemsControl.ItemsSourceProperty);
        if (bindingExpression != null)
        {
            element.SetCurrentValue(ItemsControl.ItemsSourceProperty, null);
            bindingExpression.UpdateTarget();

            var count = element.ItemsSource.OfType<object>().Count();
            if (count != element.Items.Count)
            {
                Debug.WriteLine("Wrong number of items");
            }
        }
    }
}

When the ItemsSource is set to Null, the ItemsControl does not contain any items any more and the Items collection is empt, as expected. However, when applying the binding again, all old items are restored. The Items collection contains the same instances as before. The GetEnumerator method is called, but the new items are ignored.

I don't understand why this is the case or how I can avoid that the ItemsControl caches its Items.

Alex

2
Why don't you just create a new collection and assign it to the ItemSource property?Clemens
element.SetCurrentValue(ItemsControl.ItemsSourceProperty, null); I believe this will only set your ItemsControl.ItemSource to null, where is your code to update the collection?Bolu
The collection could be shared over multiple (unconnected) objects. When I replace it at one position, I would have to replace it everywhere. But I don't have access to all these places.LionAM
@Bolu: As I said, the Items of the collection are dynamically generated on request. I updated the question to show the GetEnumerator() method of my collection. Just note that it is called - but the new items are not reflected in the ListBox.LionAM

2 Answers

2
votes

I stumbled across your post trying to figure out how to accomplish much the same thing. Not wanting to use reflection, I kept on researching and found a better answer. :)

One of the things I am still trying to get used to is how so many features in WPF are accessed not via direct calls to members of the object you're interested in, but in calls to static members which do some behind-the-scenes magic to accomplish the goal.

If I understand your scenario correctly, yours is one of those cases. In particular, while you found that getting the CollectionView object via reflection accomplished your goal, the "right" way to do this is to use the CollectionViewSource class to retrieve the CollectionView for the bound object.

Without a more complete code example, I can't say what the exact code in your scenario would look like. But the basic idea is to do something like this:

CollectionViewSource.GetDefaultView(itemsSourceObject).Refresh();

I.e. this looks up the default view for the object bound to the ItemsSource property (that is, it assumes a variable named itemsSourceObject and passes its value to the method), and then of course calls the Refresh() method to update the view.

0
votes

As ListBox.Items.Refresh() does not have the desired effect, I tried a refresh on the underlying CollectionView. And voilà - the ListBox updates. The problem with this, however, is that the only way to access this CollectionView is using Reflection, as the property is internal:

var collectionViewProperty = typeof (ItemCollection).GetProperty("CollectionView", BindingFlags.Instance | BindingFlags.NonPublic);
var collectionView = (CollectionView) collectionViewProperty.GetValue(itemsControl.Items);
collectionView.Refresh();

It seems that the CollectionView is cached for each ItemsSource that was used with the ItemsControl. Is there any way to change this behavior?