3

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?

user7134019
  • 207
  • 1
  • 3
  • 13
  • 1
    Where is the DualState property? Is DualState a property of Window? Or is it a property of some other viewmodel? – loopedcode Mar 07 '17 at 23:04
  • Look into FindAncestor – Mafii Mar 07 '17 at 23:20
  • Yes, DualState is a property of window. Since the error says 'State' property not found on 'object' ''MainWindow', the DataContext is correct (i.e.it's looking at window). The problem is that it's looking for 'State' not 'DualState' – user7134019 Mar 08 '17 at 02:18
  • How is it supposed to be looking for DualState when you bind to State...? – mm8 Mar 08 '17 at 15:41

1 Answers1

7

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();
        }
    }
}
J.H.
  • 4,232
  • 1
  • 18
  • 16
  • The problem is not the data context, as I want it to look at the window. The problem is the path. It's using 'State' literally instead of using the value the state dependency property, which would be 'DualState'. – user7134019 Mar 08 '17 at 15:52
  • I didn't mean to imply that the problem was the data context. You want to bind to the State property on the user control, correct? If so, try the answer. – J.H. Mar 08 '17 at 15:55
  • I still get the same error. I actually want to bind the DualState property of the Window. If I put the data trigger as follows it works, but I don't want to hard code it, rather pass the property name to the UC. – user7134019 Mar 08 '17 at 16:05
  • Check the updated answer. UserControl.State is bound to DualState in MainWindow.xaml. And in UserControl1.xaml, the DataTrigger is bound to State on the UserControl. – J.H. Mar 08 '17 at 16:32
  • JH - Thanks for taking the time to provide the extra detail. I had everything the same as you did except that I didn't have DualState as a dependency property on the MainWindow. When I added that it fixed the problem. Thanks! Can you help me understand why that is necessary? – user7134019 Mar 08 '17 at 17:15
  • DualState doesn't HAVE to be a DependencyProperty. I'll edit the answer with a INotifyPropertyChanged version of MainWindow.xaml.cs. What did your DualState property look like before? Did it implement INotifyPropertyChanged? – J.H. Mar 08 '17 at 17:29
  • I did have INotifyPropertyChanged implemented, but I must have missed something. The dependency property version is cleaner anyway. Thanks for the help. – user7134019 Mar 08 '17 at 18:36