Asheh
Asheh

Reputation: 1597

WPF DisplayMemberPath not Updating when SelectedItem is removed

I have simplified this problem down as much as I can. Basically I am overriding the "null" value of a combobox. So that if the item selected is deleted, it reverts back to "(null)". Unfortunately the behaviour of this is wrong, I hit delete, the ObservableCollection item is removed, thus the property binding is updated and it returns the "(null)" item as expected. But the combobox appearance shows blank. Yet the value its bound to is correct... this problem can be reproduced with the code below.

To reproduce this problem you select an item, and hit remove. Notice at this point the following line is called (when you remove the selected item). So its a good place to breakpoint.

                if (m_Selected == null)
            {
                return Items[0]; //items 0 is ItemNull
            }

Also notice that I have attmpted to fix it by Forcing a property update on the DisplayMemberPath. This did not work.

MainWindow.xaml

<Window x:Class="WPFCodeDump.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <StackPanel>
        <ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding Selected, Mode=TwoWay}" DisplayMemberPath="Name"></ComboBox>
        <Button  Click="ButtonBase_OnClick">Remove Selected</Button>
    </StackPanel>
</Window>

MainWindowViewModel.cs

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace WPFCodeDump
{
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    //Item class
    public class Item : ViewModelBase
    {
        public Item(string name)
        {
            m_Name = name;
        }

        public string Name
        {
            get { return m_Name; }
        }
        private string m_Name;

        public void ForcePropertyUpdate()
        {
            OnPropertyChanged("Name");
        }
    }

    //Item class
    public class ItemNull : Item
    {
        public ItemNull()
            : base("(null)")
        {
        }
    }

    class MainWindowViewModel : ViewModelBase
    {
        public MainWindowViewModel()
        {
            m_Items.Add(new ItemNull());
            for (int i = 0; i < 10; i++)
            {
                m_Items.Add(new Item("TestItem" + i));
            }
            Selected = null;
        }

        //Remove selected command
        public void RemoveSelected()
        {
            Items.Remove(Selected);
        }

        //The item list
        private ObservableCollection<Item> m_Items = new ObservableCollection<Item>();
        public ObservableCollection<Item> Items
        {
            get { return m_Items; }
        }

        //Selected item
        private Item m_Selected;
        public Item Selected
        {
            get
            {
                if (m_Selected == null)
                {
                    return Items[0]; //items 0 is ItemNull
                }
                return m_Selected;
            }
            set
            {
                m_Selected = value;
                OnPropertyChanged();
                if(m_Selected!=null) m_Selected.ForcePropertyUpdate();
            }
        }
    }
}

MainWindow.xaml.cs

using System.Windows;

namespace WPFCodeDump
{


    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            ((MainWindowViewModel) DataContext).RemoveSelected();
        }
    }
}

Result:

Result after pressing remove

Upvotes: 5

Views: 3628

Answers (1)

Stefan Over
Stefan Over

Reputation: 6046

A nice binding issue you found there. But as always, it's our fault, not theirs :)

The issue(s) is(are), using DisplayMemberPath with SelectedItem. The DisplayMemberPath doesn't give a f*** about the changed SelectedItem.

What you have to do, to resolve this issue, are two things:

First, in the RemoveSelected method, set the Selected property to null (to force an update on the binding):

public void RemoveSelected()
{
    Items.Remove(Selected);
    Selected = null;
}

Then, in the XAML-definition, change the bound property:

<ComboBox ItemsSource="{Binding Items}"
          SelectedValue="{Binding Selected, Mode=TwoWay}"
          DisplayMemberPath="Name"/>

Binding the SelectedValue property will correctly update the displayed text in the ComboBox.

Upvotes: 5

Related Questions