dotnet / dotnet/wpf

Trigger Binding to Child Control Not Working

Open
#2,069 5 comments 0 reactions 0 assignees View on GitHub
Documentation
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

I posted about this elsewhere but I'll report it here since it looks like a bug in WPF (using .NET 4.8)

I have this on a WPF Window



Classes defined like this

public class TestControl : ContentControl
{
static TestControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl), new FrameworkPropertyMetadata(typeof(TestControl)));
ContentProperty.OverrideMetadata(typeof(TestControl), new FrameworkPropertyMetadata(ContentChanged, CoerceContent));
}

private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var P = d as TestControl;
P.Host = e.NewValue as TestHost;
}

private static object CoerceContent(DependencyObject d, object baseValue) => baseValue as TestHost;

public static readonly DependencyPropertyKey HostProperty = DependencyProperty.RegisterReadOnly("Host", typeof(TestHost), typeof(TestControl), null);
public TestHost Host { get => (TestHost)GetValue(HostProperty.DependencyProperty); protected set => SetValue(HostProperty, value); }
}

public class TestHost : Control
{
static TestHost()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestHost), new FrameworkPropertyMetadata(typeof(TestHost)));
}

public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register("IsPlaying", typeof(bool), typeof(TestHost),
new PropertyMetadata(true));
public bool IsPlaying { get => (bool)GetValue(IsPlayingProperty); set => SetValue(IsPlayingProperty, value); }
}

Then in Generic.xaml


<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Border>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" />
<Button x:Name="MyButton" Content="False" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Value="True" Binding="{Binding Host.IsPlaying, RelativeSource={RelativeSource TemplatedParent}}">
<Setter TargetName="MyButton" Property="Content" Value="True" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>

It should display "true" in the button, but remains "false".

Setting a breakpoint when setting Host, it does get called and Host does get set correctly.

I'm getting this in the debug log after changing VS option to display all binding debug info.

System.Windows.Data Information: 41 : BindingExpression path error: 'Host' property not found for 'object' because data item is null. This could happen because the data provider has not produced any data yet. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Host.IsPlaying; DataItem=null; target element is 'TestControl' (Name=''); target property is 'NoTarget' (type 'Object')

Note that creating a normal binding outside the trigger does work

How can I get this trigger binding to work?

Well... here's "one" way


<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TestControl}">
<Border>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" />
<Button x:Name="MyButton" Content="False" Height="50" Width="100" />
<TextBox x:Name="IsPlayingHidden" Visibility="Hidden" Text="{Binding Host.IsPlaying, RelativeSource={RelativeSource TemplatedParent}}" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger SourceName="IsPlayingHidden" Property="Text" Value="True">
<Setter TargetName="MyButton" Property="Content" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>

This works, but it's ugly.

It really looks like a bug in the framework.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.