Reputation: 117
I've created a WPF user control with 2 ListBox
es (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
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