2
votes

Below is a minimal example, I couldn't possibly reduce it any more than this.

I create a live filtered CollectionView in the ViewModel like this:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows;

namespace AntiBonto.ViewModel
{
    [Serializable]
    public class Person
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public string Name { get; set; }
        public override string ToString()
        {
            return Name;
        }

        private int num;
        public int Num
        {
            get { return num; }
            set { num = value; RaisePropertyChanged(); }
        }
    }

    class ObservableCollection2<T> : ObservableCollection<T>
    {
        public ObservableCollection2() : base() { }
        public ObservableCollection2(T[] t) : base(t) { }
        public void AddRange(IEnumerable<T> collection)
        {
            foreach (var i in collection)
            {
                Items.Add(i);
            }
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }

    class MainWindow: ViewModelBase
    {
        public MainWindow() { }
        private ObservableCollection2<Person> people = new ObservableCollection2<Person>();
        public ObservableCollection2<Person> People
        {
            get
            {
                return people;
            }
            set
            {
                people = value;
                RaisePropertyChanged();
            }
        }
        public ICollectionView Team
        {
            get
            {
                CollectionViewSource cvs = new CollectionViewSource { Source = People, IsLiveFilteringRequested = true, LiveFilteringProperties = { "Num" } };
                cvs.View.Filter = p => ((Person)p).Num != 11;
                return cvs.View;
            }
        }

        public ICollectionView Ujoncok
        {
            get
            {
                CollectionViewSource cvs = new CollectionViewSource { Source = People, IsLiveFilteringRequested = true, LiveFilteringProperties = { "Num" } };
                cvs.View.Filter = p => ((Person)p).Num == 11;
                return cvs.View;
            }
        }
    }
}

The GUI has a button that modifies a Person object in the People collection:

<Window x:Class="AntiBonto.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm="clr-namespace:AntiBonto.ViewModel"
        mc:Ignorable="d"
        Title="AntiBonto" Width="1024" Height="768">
    <Window.DataContext>
        <vm:MainWindow/>
    </Window.DataContext>
    <Window.Resources>
        <FrameworkElement x:Key="DataContextProxy" DataContext="{Binding}"/> <!-- workaround, see http://stackoverflow.com/questions/7660967 -->
    </Window.Resources>
    <TabControl>
        <TabItem Header="Tab2">
            <StackPanel>
                <Button Content="Does" Click="Button_Click"/>
                <ContentControl Visibility="Collapsed" Content="{StaticResource DataContextProxy}"/>
                <!-- workaround part 2 -->
                <DataGrid ItemsSource="{Binding Ujoncok}" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False">
                    <DataGrid.Columns>
                        <DataGridComboBoxColumn Header="Who" ItemsSource="{Binding DataContext.Team, Source={StaticResource DataContextProxy}, Mode=OneWay}"/>
                    </DataGrid.Columns>
                </DataGrid>
            </StackPanel>
        </TabItem>
    </TabControl>
</Window>

I load the data from an XML file like this:

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Xml.Serialization;

namespace AntiBonto
{
    [Serializable]
    public class AppData
    {
        public Person[] Persons;
    }
    public partial class MainWindow : System.Windows.Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }
        private string filepath = "state.xml";
        private AppData AppData
        {
            get { return new AppData { Persons = viewModel.People.ToArray()}; }
            set { viewModel.People.AddRange(value.Persons);}
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var xs = new XmlSerializer(typeof(AppData));
            if (File.Exists(filepath))
            {
                using (var file = new StreamReader(filepath))
                {
                    AppData = (AppData)xs.Deserialize(file);
                }
            }
        }     

        private ViewModel.MainWindow viewModel { get { return (ViewModel.MainWindow)DataContext; } }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Person p = viewModel.People.First(q => q.Name == "Ferencz Katalin");
            if (p.Num == 11)
                p.Num = 0;
            else
                p.Num= 11;
        }
    }
}

and the XML file is this:

<?xml version="1.0" encoding="utf-8"?>
<AppData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Persons>
    <Person>
      <Name>Person1</Name>
      <Num>0</Num>
    </Person>
    <Person>
      <Name>Person2</Name>
      <Num>0</Num>
    </Person>
  </Persons>
</AppData>

When I click the button once or twice, I get a NullReference exception. There is no inner exception. The exception does not arise in my code, but in framework code, so it does not show the source, I cannot find out which object is null and where the exception comes from. I didn't manage to set up "stepping into .NET sources", it still tells me that there is no source available.

Here's a stack trace:

at System.Windows.Data.ListCollectionView.RestoreLiveShaping() at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.DispatcherOperation.InvokeImpl() at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at MS.Internal.CulturePreservingExecutionContext.Run(CulturePreservingExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Threading.DispatcherOperation.Invoke() at System.Windows.Threading.Dispatcher.ProcessQueue() at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame) at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame) at System.Windows.Application.RunDispatcher(Object ignore) at System.Windows.Application.RunInternal(Window window) at System.Windows.Application.Run(Window window) at System.Windows.Application.Run() at AntiBonto.App.Main() in D:\Marci\Programozás\AntiBonto\AntiBonto\obj\Debug\App.g.cs:line 0 at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

2
@RenéVogt No, it's not.marczellm
I wouldn't call this a duplicate, simply because it shares the same very generic exception as another question.Joe
@Joe: It may not be an exact duplicate of the canonical "What is a NullReferenceException...` question, but this question still does exhibit a lack of research, and fails to provide a good minimal reproducible example that reliably reproduces the problem. If the problem can be reproduced only using the third-party library, then the right place to seek help is the author of that library. If the library isn't needed, then a question here might be appropriate, but the question needs to include a reproducible example.Peter Duniho
@Peter Duniho Almost all questions on Stackoverflow fail to provide Minimal, Complete, and Verifiable examples (sometimes it's not possible). This guy did his research, he found the only other bit of info in the internet about this issue (which is my question, which faced the same issues of people saying it was a duplicate). Yes, using an external library isn't ideal, but it doesn't invalidate the question. People don't like the question because it has a simple exception that a lot of new coders face, and jump to conclusions.Joe
I have reopened the question, since this is about a NRE in the .NET BCL (apparently a bug in the ListCollectionView implementation), not in the OP's code. Thus, the solution described in the dupe does not apply.Heinzi

2 Answers

6
votes

I have no idea why, but this fixed the bug:

public ICollectionView Team
{
    get
    {
        CollectionViewSource cvs = new CollectionViewSource { Source = People, IsLiveFilteringRequested = true, LiveFilteringProperties = { "Num" } };
        cvs.View.Filter = p => ((Person)p).Num != 11;
        cvs.View.CollectionChanged += EmptyEventHandler;
        return cvs.View;
    }
}
private void EmptyEventHandler(object sender, NotifyCollectionChangedEventArgs e) { }

I was trying to debug where the exception happens, and I wanted to set a breakpoint when the collection changes. Subscribing to the event made the exception go away.

0
votes

I spent a long time trying to debug System.Windows and got nowhere, you're welcome to try.

In terms of a band-aid workaround that will at least function, from my question I found that new CollectionViewSource causes the issue, whereas CollectionViewSource.GetDefaultView does not.

You mention there are a two issues with this:

1) I am modifying the base collection all the time and need my filters to refresh

You can work around this by using ObservableCollections and listening for editions, then updating your filtered instances.

So for each new time you need a CollectionViewSource, create a new ObservableCollection, and keep track of it, add a listener to the original ObservableCollection and push updates to the CollectionViewSource's version.

Elegant? No. Functional? Yes.

2) I'm using Live Shaping which is not available with GetDefaultView.

It's always been available for me using GetDefaultView, can you show where you are creating the view?

One similarity I've noticed is that I'm using an extension of ObservableCollection. Do you have the issue if you just use a standard ObservableCollection?