views:

45

answers:

3

I have two collections displayed in my WPF application, and I'd like to have elements from one of them disabled in the other. Doing this I'm creating a custom control FilteringListBox that inherits ListBox, and I want to add some handling inside it to disable elements that are set in a collection set through a property on the FilteringListBox. Now, my problem is that the dependency property taking the ObservableCollection that I want to filter elements from isn't set - even if I bind to it in the xaml.

I've created a simplified app where I reproduce the problem. Here is my Xaml:

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <StackPanel Orientation="Vertical">
            <TextBlock>Included</TextBlock>
            <ListBox x:Name="IncludedFooList" ItemsSource="{Binding IncludedFoos}"></ListBox>
        </StackPanel>
        <Button Margin="10" Click="Button_Click">Add selected</Button>
        <StackPanel Orientation="Vertical">
            <TextBlock>Available</TextBlock>
            <Listbox:FilteringListBox x:Name="AvailableFooList" ItemsSource="{Binding AvailableFoos}" FilteringCollection="{Binding IncludedFoos}"></Listbox:FilteringListBox>
        </StackPanel>                
    </StackPanel>            
</StackPanel>

This is my custom component - currently only holding the Dependency Property:

public class FilteringListBox : ListBox
{
    public static readonly DependencyProperty FilteringCollectionProperty =
        DependencyProperty.Register("FilteringCollection", typeof(ObservableCollection<Foo>), typeof(FilteringListBox));                                                 

    public ObservableCollection<Foo> FilteringCollection
    {
        get
        {
            return (ObservableCollection<Foo>)GetValue(FilteringCollectionProperty);
        }
        set
        {
            SetValue(FilteringCollectionProperty, value);
        }
    }
}

And for the complete code the code behind and class definitions are here:

public partial class MainWindow : Window
{
    private MainViewModel _vm;

    public MainWindow()
    {
        InitializeComponent();
        _vm = new MainViewModel();
        DataContext = _vm;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        if (AvailableFooList.SelectedItem == null)
            return;
        var selectedFoo = AvailableFooList.SelectedItem as Foo;
        _vm.IncludedFoos.Add(selectedFoo);
    }
}

public class MainViewModel
{
    public MainViewModel()
    {
        IncludedFoos = new ObservableCollection<Foo>();
        AvailableFoos = new ObservableCollection<Foo>();
        GenerateAvailableFoos(); 
    }

    private void GenerateAvailableFoos()
    {
        AvailableFoos.Add(new Foo { Text = "Number1" });
        AvailableFoos.Add(new Foo { Text = "Number2" });
        AvailableFoos.Add(new Foo { Text = "Number3" });
        AvailableFoos.Add(new Foo { Text = "Number4" });
    }

    public ObservableCollection<Foo> IncludedFoos { get; set; }
    public ObservableCollection<Foo> AvailableFoos { get; set; }
}

public class Foo
{
    public string Text { get; set; }
    public override string ToString()
    {
        return Text;
    }
}

I add breakpoints to the setter and getter of the DependencyProperty FilteringCollection in the FilteringListBox, but it is never triggered. Why? How can I fix it?

+5  A: 

The binding system bypasses set and get accessors for dependency properties. If you want to execute code when a dependency property changes, you should add a PropertyChangedCallback to the DependencyProperty definition.

Bubblewrap
+1. I wish it were more simple... some better convention to create DependancyProperties... but this is what we have to live with. The getter/setter is simply a convenience to the developer but bears no meaning to data binding. It is actually the other way around. The binding system calls `GetValue` and `SetValue` and the property just does it without binding. The event is fired when `SetValue` is called. It sucks that it is so confusing.
Brian Genisio
Hmm.. Not very intuitive or convenient, but that fixed it. Thanks!
stiank81
+1  A: 

MSDN has a section about Dependency Property Callbacks and Validation, you need to register a PropertyChangedCallback

Example from msdn

public static readonly DependencyProperty AquariumGraphicProperty 
= DependencyProperty.Register(
  "AquariumGraphic",
  typeof(Uri),
  typeof(AquariumObject),
  new FrameworkPropertyMetadata(null,
      FrameworkPropertyMetadataOptions.AffectsRender, 
      new PropertyChangedCallback(OnUriChanged)
  )
);

private static void OnUriChanged(DependencyObject d, 
                                 DependencyPropertyChangedEventArgs e) {
  Shape sh = (Shape) d;
  sh.Fill = new ImageBrush(new BitmapImage((Uri)e.NewValue));
}
Filip Ekberg
+1  A: 

The get and set properties are never used directly by the WPF framework. You only provide them as a convenience for yourself. Instead you need to add a callback to your dependency property registration. The callback will be called when a value is bound to the dependency property. Hence your code for FilteredListBox should be changed to something similar to the following:

public partial class FilteringListBox : ListBox
{
    public static readonly DependencyProperty FilteringCollectionProperty =
        DependencyProperty.Register("FilteringCollection", typeof(ObservableCollection<Foo>), typeof(FilteringListBox), 
        new PropertyMetadata(null, FilteringCollectionPropertyCallback));

    static void FilteringCollectionPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FilteringListBox listbox = d as FilteringListBox;
        // Do some work here
    }

    public ObservableCollection<Foo> FilteringCollection
    {
        get
        {
            return (ObservableCollection<Foo>) GetValue(FilteringCollectionProperty);
        }
        set
        {
            SetValue(FilteringCollectionProperty, value);
        }
    }
}
Jakob Christensen
You do realize that the default value is held statically and in this case is used by reference? Every FilteringListBox will now by default refer to the same instance of ObservableCollection<Foo> unless the value is overridden.
Bubblewrap
Good point. The default value is unneccessary in this case. It is being overriden by the binding to the viewmodel.
Jakob Christensen