Montag, 21. November 2011

Binding to the SelectedItems of ListBox using attached properties

In the last post I wrote about how to extend the ListBox to enable a DataBinding to the SelectedItems property.
It is not always possible to extend a control, so I took another look at the post by Marlon Grech where he uses a attached property to do the trick. I merged the two examples and resulted with a attached property that can handle a two way binding.

The only thing with this approach is that the collection, that the SelectedItems bind to, has to be created on initialization of the ViewModel

Here is the code to the attached property:
 public class ListBoxHelper  
 {  
    #region SelectedItems  
   
    ///  
    /// SelectedItems Attached Dependency Property  
    ///  
    public static readonly DependencyProperty SelectedItemsProperty =  
       DependencyProperty.RegisterAttached("SelectedItems", typeof (IList), 
                          typeof (ListBoxHelper),  
                          new FrameworkPropertyMetadata((IList) null,  
                                       new PropertyChangedCallback(OnSelectedItemsChanged)));  
   
    /// <summary>  
    /// Gets the SelectedItems property.  
    /// </summary>  
    /// <param name="d"></param>  
    /// <returns></returns>  
    public static IList GetSelectedItems(DependencyObject d)  
    {  
       return (IList) d.GetValue(SelectedItemsProperty);  
    }  
   
    /// <summary>  
    /// Sets the SelectedItems property.  
    /// </summary>  
    /// <param name="d"></param>  
    /// <param name="value"></param>  
    public static void SetSelectedItems(DependencyObject d, IList value)  
    {  
       d.SetValue(SelectedItemsProperty, value);  
    }  
   
    /// <summary>  
    /// Called when SelectedItems is set  
    /// </summary>  
    /// <param name="d"></param>  
    /// <param name="e"></param>  
    private static void OnSelectedItemsChanged(DependencyObject d, 
                                             DependencyPropertyChangedEventArgs e)  
    {  
       var listBox = (ListBox) d;  
       SetInternalSelectedItemsToPublic(listBox);  
       IList selectedItems = GetSelectedItems(listBox);  
   
       if (selectedItems is ObservableCollection<object>)  
       {  
          // if the list is a observable collection binde to the changed event 
          // to update the internal collection
          (selectedItems as ObservableCollection<object>).CollectionChanged += delegate  
                             {  
                                        SetSelectedDataItemsToInternal(listBox);  
                             };  
       }  
   
       listBox.SelectionChanged += delegate  
                      {  
                               SetInternalSelectedItemsToPublic(listBox);  
                      };  
    }  
   
   
    #region Implementation  
   
    static bool _isChanging;  
   
    private static void SetSelectedDataItemsToInternal(ListBox listBox)  
    {  
       if (_isChanging)  
          return;  
   
       _isChanging = true;  
   
       var selectedDataItems = GetSelectedItems(listBox);  
   
       if (listBox.SelectedItems != null)  
       {  
          listBox.SelectedItems.Clear();  
   
          if (selectedDataItems == null)  
          {  
             _isChanging = false;  
             return;  
          }  
   
          foreach (var item in selectedDataItems)  
             listBox.SelectedItems.Add(item);  
       }  
   
       _isChanging = false;  
    }  
   
    private static void SetInternalSelectedItemsToPublic(ListBox listBox)  
    {  
       if (_isChanging)  
          return;  
   
       _isChanging = true;  
   
       var selectedDataItems = GetSelectedItems(listBox);  
   
       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 (listBox.SelectedItems != null)  
       {  
          foreach (var item in listBox.SelectedItems)  
             selectedDataItems.Add(item);  
       }  
   
       _isChanging = false;  
    }  
         
    #endregion  
   
    #endregion  
 }  

And here is the XAML with the binding:
<ListBox ItemsSource="{Binding ListItems}" 
   c:ListBoxHelper.SelectedItems="{Binding SlectedListItems}" 
   SelectionMode="Multiple"/>

Keine Kommentare: