Chad Ernst
Chad Ernst

Reputation: 117

User Control databinding issue with listbox and MVVM

I've created a WPF user control with 2 ListBoxes (named "Source" and "Value") and a "Add" button that allow the user to "copy" items that are bound to the Source ListBox to the Value ListBox. Here's the code:

public partial class MultiListSelect : UserControl
{
    public static readonly DependencyProperty ListSourceProperty;
    public static readonly DependencyProperty ListValueProperty;

    public IEnumerable ListSource  
    {
        get { return (ObservableCollection<object>)GetValue(MultiListSelect.ListSourceProperty); } 
        set { SetValue(MultiListSelect.ListSourceProperty, value); }  
    }

    public IEnumerable ListValue
    {
        get { return (ObservableCollection<object>)GetValue(MultiListSelect.ListValueProperty); }
        set { SetValue(MultiListSelect.ListValueProperty, value); }
    }  


    public MultiListSelect()
    {
        InitializeComponent();
    }

    static MultiListSelect()  
     { 

         MultiListSelect.ListSourceProperty =  
            DependencyProperty.Register("ListSource",
            typeof(IEnumerable), typeof(MultiListSelect));

         MultiListSelect.ListValueProperty =
            DependencyProperty.Register("ListValue",
            typeof(IEnumerable), typeof(MultiListSelect));

     }

    private void btnAdd_Click(object sender, RoutedEventArgs e)
    {
        foreach (object o in listBoxSource.SelectedItems)
        {
             listBoxValue.Items.Add(o);
        }
    }

}

My view that consumes this user control is using MVVM Light. Here's my ViewModel code that exposes the data that gets bound to each ListBox:

public class ProfileViewModel : ViewModelBase
{
    public User User { get; set; }

    // This gets bound to the Source listbox
    private ObservableCollection<OperatingArea> _operatingAreas;
    public ObservableCollection<OperatingArea> OperatingAreas 
    { 
        get { return _operatingAreas; }
        private set
        {
            _operatingAreas = value;
        }
    }

    public ProfileViewModel()
    {
        OperatingAreas = _rpDomain.LoadOperatingAreas();
        WindowsIdentity identity = WindowsIdentity.GetCurrent();

        if (User == null)
        {
            User = _rpDomain.CreateUser();
            User.Preferences = _rpDomain.CreateUserPreferences();
            User.UserId = identity.Name;

            _rpDomain.Add(User);
        }

        if (User.Preferences == null)
        {
            User.Preferences = _rpDomain.CreateUserPreferences();
        }

        if (User.Preferences.ViewableOperatingAreas == null)
        {
            // This gets bound to my 'Value' ListBox
            User.Preferences.ViewableOperatingAreas = new ObservableCollection<OperatingArea>();
        }
    }
}

The binding is working okay. The problem is that when the user selects items in the "Source" list box and clicks the "Add" button, I get an error when this line executes in the code behind of the user control in the btnAdd_Click event handler:

listBoxValue.Items.Add(o);

The error I receive is: 'Operation not valid while ItemSource is in use. Access and modify elements with ItemsControl.ItemsSource instead'. So, I understand the issue but I don't know the best way to resolve this. The DependencyProperties are of type IEnumerable so I can't add to them directly in the code behind of the user control. Also I can't access the ViewModel and update the properties directly because the user control library can't reference the ViewModel for the Views. What's the best way to copy items from the "Source" list box to the "Value" list box (prefer to have code contained in the user control vs. requiring the View/ViewModel to add the items). Also, this is a user control so the type that gets bound to each list box varies with each usage of the control.

EDIT - 10-4-2011

So, the real question is how to do I update (add to) the "Value" list from within my user control? The dependency property is of type IEnumerable, so it can't be updated directly, and as shown with the error above I can't simply call listbox.items.Add(). I'm using MVVM, so databinding to properties exposed in the ViewModel is a requirement. Also the user control doensn't know (and shouldn't know) the type that's being bound to it at runtime. I realize my user control could raise an "Added" event that could be handled via EventToCommand feature in MVVM Light, but the seems unnecessary - I personally wouldn't be satisfied if I purchased a control from a 3rd party control vendor that required me to update the collection myself. I know this is possible because this is essentially the way a combobox works - the combobox has both an ItemsSource property and SelectedValue property that can be bound to properties on the ViewModel.

<ComboBox 
    ItemsSource="{Binding Path=Categories}"
    DisplayMemberPath="Name"
    SelectedValue="{Binding Path=SelectedCategory, Mode=TwoWay}"/> 

Upvotes: 1

Views: 924

Answers (1)

user572559
user572559

Reputation:

Items and ItemsSource are mutually exclusive, once ItemsSource is set to something, Items.Add(..) will throw. Don't use ItemsSource even though it's the simplest option, use Items.Add within a loop (you can call it from within DP value changed event handler).

Please let me know if you need any further help.

Upvotes: 0

Related Questions