A short while ago I needed to get access to the SelectedItems with DataBinding in my MVVM implementation. The SelectedItems property in the ListBox is read only and so there is no way to have a Binding to it. While searching for a solution I stumbled upon this
post by Marlon Grech.
He explains how to create a bindable property for the SelectedItems using attached properties.
The problem with his example is that the binding only works one way when the user selects items in the ListBox. I needed to have a way where I could select items from the ViewModel as well.
The other thing was that I already had an extension of the ListBox.
So instead of creating an attached property I simply took the example and extended the ListBox with the SelectedDataItems property.
Here is the code to the extended ListBox
public class ListBoxExt : ListBox
{
static ListBoxExt()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof (ListBoxExt),
new FrameworkPropertyMetadata(typeof (ListBoxExt)));
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
SetInternalSelectedItemsToPublic();
base.OnSelectionChanged(e);
}
#region SelectedDataItems
public static readonly DependencyProperty SelectedDataItemsProperty =
DependencyProperty.Register(
"SelectedDataItems",
typeof(IList),
typeof(ListBoxExt),
new FrameworkPropertyMetadata(
(IList) null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedDataItemsPropertyChangedCallback)));
/// <summary>
/// Gets an IList containing all selected items
/// </summary>
public IList SelectedDataItems
{
get
{
return (IList) GetValue(SelectedDataItemsProperty);
}
set
{
SetValue(SelectedDataItemsProperty, value);
}
}
/// <summary>
/// Handles changes to the SelectedDataItems property.
/// </summary>
private static void OnSelectedDataItemsPropertyChangedCallback(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var listBox = d as ListBoxExt;
if (listBox == null)
return;
listBox.SetSelectedDataItemsCollection(e.NewValue as IList);
}
private void SetSelectedDataItemsCollection(IList selection)
{
if (_isChanging)
return;
SetSelectedDataItemsToInternal(selection);
if (selection != null && selection is ObservableCollection<object>)
{
// add a eventlistner to keep a two way binding
(selection as ObservableCollection<object>).CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
BoundSelectedDataItemsCollectionChanged);
}
}
#region Implementation
bool _isChanging;
private void SetSelectedDataItemsToInternal(IList selectedDataItems)
{
if (_isChanging)
return;
_isChanging = true;
if (SelectedItems != null)
{
SelectedItems.Clear();
if (selectedDataItems == null)
{
_isChanging = false;
return;
}
foreach (var item in selectedDataItems)
SelectedItems.Add(item);
}
_isChanging = false;
}
private void SetInternalSelectedItemsToPublic()
{
if (_isChanging)
return;
_isChanging = true;
if (SelectedDataItems == null)
{
var selection = new ObservableCollection<object>();
selection.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
BoundSelectedDataItemsCollectionChanged);
SelectedDataItems = selection;
}
if (SelectedDataItems == null)
{
// the bound data type is not of typ IList / IList<object> /
// IEnumerable / Ienumerable<object>
// in this case the bound property has to be initialized first by the caller
_isChanging = false;
return;
}
SelectedDataItems.Clear();
if (base.SelectedItems != null)
{
foreach (var item in base.SelectedItems)
SelectedDataItems.Add(item);
}
_isChanging = false;
}
/// <summary>
/// gets executed when the bound collection is of type ObservableCollection
/// and the collection gets changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void BoundSelectedDataItemsCollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (_isChanging)
return;
if (SelectedDataItems == null)
return;
SetSelectedDataItemsToInternal(SelectedDataItems);
}
#endregion
#endregion
}
To be able to create a two way binding, the property for the SelectedDataItems has to be IList, IList<object>, IEnumerable or IEnumerable<object>. If a concrete type is used in the generic enumerations the binding will only work one way to the list.
Here is the XAML with the bindings:
<c:ListBoxExt ItemsSource="{Binding ListItems}"
SelectedDataItems="{Binding SlectedListItems}"
SelectionMode="Multiple"/>
Keine Kommentare:
Kommentar veröffentlichen