Why isn't my dependency property set when binding to it?
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 Fil开发者_如何学GoteringListBox. 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?
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.
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));
}
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);
}
}
}
精彩评论