Reputation: 217
I've got a simple user control that contains an image whose source I want to change based on a property in the parent (which could be another UC or a Window). A simplified version of the UC looks like this
<UserControl x:Class="Test.Controls.DualStateButton" ... x:Name="root">
<Grid>
<Image Height="{Binding Height, ElementName=root}" Stretch="Fill" Width="{Binding Width, ElementName=root}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{Binding ImageOff, ElementName=root}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="True">
<Setter Property="Source" Value="{Binding ImageOn, ElementName=root}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Grid>
</UserControl>
Height, Width, ImageOff, ImageOn, and State are all dependency properties on the UC. The UC has no DataContext set, so it should be inheriting the parent. What I'm trying to do is something like the following where the State in the UC is bound to the DualState property of the Window.
<Window x:Class="Test.MainWindow" DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<Grid>
<local:DualStateButton State="{Binding DualState}" Height="100" ImageOff="{StaticResource ButtonUp}" ImageOn="{StaticResource ButtonDown}" Width="100"/>
</Grid>
</Window>
What I get, however, is a error saying that 'State' property not found on 'object' ''MainWindow', so it appears to be taking the binding 'State' in the UC literally and not assigning it to the DualState property of the Window. Can someone shed some insight on what I'm doing wrong?
If I set the State property on the UC either through code or XAML (as a bool value) it works fine. The State DP is defined as follows.
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(bool), typeof(DualStateButton),
new PropertyMetadata(false));
public bool State
{
get { return (bool)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
Does it data type need to be a binding or something in order for this to work?
Upvotes: 3
Views: 15099
Reputation: 4322
The DataContext for the DataTrigger is set to the window, that's why it looks at the window for "State". You just need to tell the binding that State is on the user control. Try this:
<DataTrigger Binding="{Binding Path=State, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" Value="True">
Here is a complete example:
MainWindow.xaml
<Window x:Class="WpfApplication89.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication89"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:UserControl1 State="{Binding Path=DualState}" />
<CheckBox Content="DualState" IsChecked="{Binding DualState}" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace WpfApplication89
{
public partial class MainWindow : Window
{
public static readonly DependencyProperty DualStateProperty = DependencyProperty.Register("DualState", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
public bool DualState
{
get { return (bool)GetValue(DualStateProperty); }
set { SetValue(DualStateProperty, value); }
}
public MainWindow()
{
InitializeComponent();
}
}
}
UserControl1.xaml
<UserControl x:Class="WpfApplication89.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication89"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="User Control 1">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Background" Value="Beige" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=State, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" Value="true">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</UserControl>
UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication89
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty StateProperty = DependencyProperty.Register("State", typeof(bool), typeof(UserControl1), new PropertyMetadata(false));
public bool State
{
get { return (bool)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public UserControl1()
{
InitializeComponent();
}
}
}
MainWindow.xaml.cs (INotifyPropertyChanged version)
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApplication89
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName]string name = null)
{
if (Equals(field, value))
{
return false;
}
field = value;
this.OnPropertyChanged(name);
return true;
}
protected void OnPropertyChanged([CallerMemberName]string name = null)
{
var handler = this.PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
#region Property bool DualState
private bool _DualState;
public bool DualState { get { return _DualState; } set { SetProperty(ref _DualState, value); } }
#endregion
public MainWindow()
{
InitializeComponent();
}
}
}
Upvotes: 7