I have a View MainWindow.xaml which contains two buttons which are defined in Button.xaml. The button binds to a property IsVisible
which defines whether the button is visible.
MainWindow.xaml:
<local:Button DataContext="{Binding ButtonViewModel1}" />
<local:Button DataContext="{Binding ButtonViewModel2}" />
Button.xaml:
<StackPanel>
<Button Name="MyButton" Visibility="{Binding IsVisible}">
<TextBlock>My Button</TextBlock>
</Button>
</StackPanel>
For the Button I have two ViewModels: ButtonViewModel
and ButtonViewModelChild
.
ButtonViewModelChild
inherits from ButtonViewModel
. Both provide the IsVisible
property:
ButtonViewModel:
public Visibility IsVisible
{
get
{
return Visibility.Hidden;
}
}
ButtonViewModelChild:
public new Visibility IsVisible
{
get
{
return Visibility.Visible;
}
}
The ViewModel of MainWindow.xaml contains a property ButtonViewModel1
and ButtonViewModel2
. These properties look like this:
public ButtonViewModel ButtonViewModel1
{
get
{
return new ButtonViewModelChild();
}
}
public ButtonViewModelChildButtonViewModel2
{
get
{
return new ButtonViewModelChild();
}
}
The button should be invisible if ButtonViewModel
is used as DataContext for the button and visible if ButtonViewModelChild
is used.
I assumed that the button which uses ButtonViewModel1 as DataContext gets a ButtonViewModel
and the other button a ButtonViewModelChild
. So the first button would not be visible and the second would. Both buttons are visible though. What does WPF do here? Obviously It uses both times the inherited view of the instance. Is there a way to tell WPF to use the return type of the property and not the created-instance type?
I am aware that method hiding is not the best practice. I will probably change the design but I am curious why the result is not as expected.
The property in a binding path is resolved by reflection, hence it finds the subclass property, regardless of the ButtonViewModel1 property type.
public ButtonViewModel ButtonViewModel1
{
get { return new ButtonViewModelChild(); } // subclass instance
}
To avoid that, you would have to return a ButtonViewModel
instance from the getter:
public ButtonViewModel ButtonViewModel1
{
get { return new ButtonViewModel(); }
}
You could as well declare the property as object
, the binding would still work:
public object ButtonViewModel1
{
get { return new ButtonViewModelChild(); }
}
Besides that, you should generally not let a UserControl operate an a specific view model (and thus make it dependent of the viel model class). Instead you should declare a IsButtonVisible
dependency property in the UserControl class, and bind bind it like this:
<StackPanel>
<Button Visibility="{Binding IsButtonVisible,
RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock>My Button</TextBlock>
</Button>
</StackPanel>
Usage would be:
<local:Button IsButtonVisible="{Binding ButtonViewModel1.IsVisible}" />