Search code examples
c#wpfxamlmvvm-lightmef

ComboBox ItemTemplate triggering TargetException when selected item is removed?


I have a WPF application (.NET 4.5) using MEF and MVVMLight. There is a shared MEF Export called "DeviceSupervisor" which holds an ObservableCollection of a custom class, DeviceConfiguration, which isnt much more than a few string properties.

In the main app, you can add and remove devices to/from this collection. One of these modules displays the list of devices in a ComboBox for choosing.

Explicit Conditions:

  1. Have a number of items added to the collection via the main app
  2. Select one of the items from the drop down in the module
  3. Remove the item that happens to be selected from the collection via the main app

Code to sync user changes to DeviceSupervisor:

foreach (DeviceComplete set in Devices)
{
    set.Configuration.CommunicationDetails = set.Device.Value.CommunicationDetails;
    if (DeviceSupervisor.AvailableDevices.Any(d => d.ID == set.Configuration.ID))
    {
        DeviceSupervisor.AvailableDevices
            .Single(d => d.ID == set.Configuration.ID)
            .CommunicationDetails = set.Configuration.CommunicationDetails;
    }
    else
    {
        DeviceSupervisor.AvailableDevices.Add(set.Configuration);
    }
}

var missing = new HashSet<DeviceConfiguration>(DeviceSupervisor.AvailableDevices
        .Except(Devices.Select(d => d.Configuration)));
foreach (DeviceConfiguration toRemove in missing)
{
    // --- TargetException thrown here ---
    DeviceSupervisor.AvailableDevices.Remove(toRemove);
}

CollectionViewSource in XAML on the module:

<CollectionViewSource x:Key="AvailableDevicesCvs"
        Source="{Binding Path=AvailableDevices, Mode=OneWay}" Filter="AvailableDevicesCVS_Filter">
</CollectionViewSource>

ComboBox:

<ComboBox HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top" Width="120"
        ItemsSource="{Binding Source={StaticResource AvailableDevicesCvs}}"
        SelectedItem="{Binding Path=SelectedDevice}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=CommunicationDetails}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Exception:

An unhandled exception of type 'System.Reflection.TargetException' occurred in mscorlib.dll Additional information: Object does not match target type.

The stack trace is almost all WindowsBase and PresentationFramework references that are grayed out. Intellitrace only shows the exception being thrown on the line that I commented above.

My Findings / Other Notes

Note that this exception is not thrown if the ComboBox has another item selected than the removed. Throwing explicit one-way bindings everywhere had no affect. The exception is thrown before any breakpoints I can throw in CollectionChanged or Filter events.

After a lot of struggling, reorganizing, and trial and error, I found that if I simply remove the ItemTemplate from the combobox, then everything works as expected - no exceptions and the ComboBox adjusts the selection to compensate for the missing item.

I'm nervous that I'm caught in some tunnel vision trap and can't see the silly mistake I made - but more nervous that its something else.


Solution

  • I've since traced the issue to the CommunicationDetails property. The library with the source file for that class did not have a reference to MVVMLight, and as a quick shortcut to get a xyzChanged even (for other uses) I wrote the property like the following:

    Old

    private string mCommunicationDetails;
    public string CommunicationDetails
    {
        get
        {
            return mCommunicationDetails;
        }
        set
        {
            bool changed = (mCommunicationDetails != value);
            mCommunicationDetails = value;
            if (changed)
            {
                CommunicationDetailsChanged.Raise(this, EventArgs.Empty);
            }
        }
    }
    public event EventHandler CommunicationDetailsChanged;
    

    Replacing it with a vanilla {get;set;} property works. I've since replaced it with an MVVMLight property

    New

    private string mCommunicationDetails;
    public string CommunicationDetails
    {
        get
        {
            return mCommunicationDetails;
        }
        set
        {
            if (Set(() => CommunicationDetails, ref mCommunicationDetails, value))
            {
                CommunicationDetailsChanged.Raise(this, EventArgs.Empty);
            }
        }
    }
    public event EventHandler CommunicationDetailsChanged;
    

    And it works.

    If anyone cares to take the time to find or explain the real source of this error I'll accept that as the answer, but I'm accepting this for now to close the issue.