0

I'm designing a control in WPF, which contains a very common pattern: button opening a dropdown. The relevant part of XAML looks like following:

<ToggleButton x:Name="btnFilterPopup" IsChecked="{Binding IsOpen, ElementName=filterPopup, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" Margin="{StaticResource DialogItemsExceptLeftMargin}" FontFamily="Marlett" Content="6"/>
<Popup x:Name="filterPopup" PlacementTarget="{Binding ElementName=btnFilterPopup}" Placement="Bottom">
   <Border Background="{StaticResource ToolPopupBackgroundBrush}">
      <StackPanel Orientation="Vertical" Margin="{StaticResource DialogItemsMargin}">
         <CheckBox IsChecked="{Binding FilterCaseSensitive, Mode=TwoWay}" Margin="{StaticResource DialogItemsMargin}">Case sensitive</CheckBox>
         <CheckBox IsChecked="{Binding FilterExcludes, Mode=TwoWay}" Margin="{StaticResource DialogItemsExceptTopMargin}">Exclude matching</CheckBox>
      </StackPanel>
   </Border>
</Popup>

However, neither binding can find its target. Diagnostics looks like following:

System.Windows.Data Warning: 67 : BindingExpression (hash=46479497): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=46479497): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name filterPopup:  queried ToggleButton (hash=36168141)
System.Windows.Data Warning: 67 : BindingExpression (hash=46479497): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=46479497): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name filterPopup:  queried ToggleButton (hash=36168141)
System.Windows.Data Warning: 67 : BindingExpression (hash=46479497): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=46479497): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name filterPopup:  queried ToggleButton (hash=36168141)
System.Windows.Data Warning: 67 : BindingExpression (hash=46479497): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=46479497): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name filterPopup:  queried ToggleButton (hash=36168141)
System.Windows.Data Warning: 67 : BindingExpression (hash=46479497): Resolving source  (last chance)
System.Windows.Data Warning: 70 : BindingExpression (hash=46479497): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name filterPopup:  queried ToggleButton (hash=36168141)
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=filterPopup'. BindingExpression:Path=IsOpen; DataItem=null; target element is 'ToggleButton' (Name='btnFilterPopup'); target property is 'IsChecked' (type 'Nullable`1')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=btnFilterPopup'. BindingExpression:(no path); DataItem=null; target element is 'Popup' (Name='filterPopup'); target property is 'PlacementTarget' (type 'UIElement')

Why the bindings cannot find their TargetElements?

thatguy
  • 21,059
  • 6
  • 30
  • 40
Spook
  • 25,318
  • 18
  • 90
  • 167
  • 2
    I can't reproduce the problem, for me everything seems to work using your code: popup is under the button, checkbox changing properties. Did you set a `DataContext` for your window? Please refer to [mcve]. – Sinatr Jan 20 '21 at 09:04
  • Maybe you are changing `DataContext` somewhere? In parent container of `Popup` and `ToggleButton`? – Sinatr Jan 20 '21 at 09:08
  • @Sinatr See my comment to Lennart's answer, I'll repeat it here for your convenience: Surprisingly, when I cut and paste those two controls directly to main window, they work fine. They are placed inside a UserControl embedded inside a TabControl (manually, like ) – Spook Jan 20 '21 at 11:08

2 Answers2

1

Some controls like Popups aren't part of the visual tree and are thus unable to be reached via that kind of binding. In your case I would bind the IsOpen property to a property in your VM and use that for the IsChecked binding. Same for the PlacementTarget binding, if you don't want to or can't explicitly set it in code behind.

Lennart
  • 9,657
  • 16
  • 68
  • 84
  • Initially both bindings were inside Popup, so it tried to reach the button instead, but couldn't as well... (i mean: `IsOpen="{Binding ...}"`) – Spook Jan 20 '21 at 08:41
  • @Spook You might need to set the DataContext on the Popup as well – Lennart Jan 20 '21 at 08:42
  • Surprisingly, when I cut and paste those two controls directly to main window, they work fine. They are placed inside a UserControl embedded inside a TabControl (manually, like ``) – Spook Jan 20 '21 at 08:54
  • In your answer you are probably refering to [relative source binding in popup](https://stackoverflow.com/q/14939632/1997232) error. Otherwise I don't see how visual tree is relevant here. DataContext is inherited, setting `PlacementTarget` seems ok too. – Sinatr Jan 20 '21 at 08:56
1

A Popup is not part of the same visual tree as the placement target. You can use a RelativeSource binding to the Popup itself, to access the DataContext of the corresponding PlacementTarget.

<ToggleButton x:Name="btnFilterPopup" IsChecked="{Binding IsOpen, ElementName=filterPopup, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" Margin="{StaticResource DialogItemsExceptLeftMargin}" FontFamily="Marlett" Content="6"/>
<Popup x:Name="filterPopup" PlacementTarget="{Binding ElementName=btnFilterPopup}" Placement="Bottom">
   <Border Background="{StaticResource ToolPopupBackgroundBrush}">
      <StackPanel Orientation="Vertical" Margin="{StaticResource DialogItemsMargin}">
         <CheckBox IsChecked="{Binding PlacementTarget.DataContext.FilterCaseSensitive, Mode=TwoWay, RelativeSource={RelativeSource AncestorType={x:Type Popup}}}" Margin="{StaticResource DialogItemsMargin}">Case sensitive</CheckBox>
         <CheckBox IsChecked="{Binding PlacementTarget.DataContext.FilterExcludes, Mode=TwoWay, RelativeSource={RelativeSource AncestorType={x:Type Popup}}}">Exclude matching</CheckBox>
      </StackPanel>
   </Border>
</Popup>

Alternatively, set the DataContext of the Popup referring to its PlacementTarget.

<ToggleButton x:Name="btnFilterPopup" IsChecked="{Binding IsOpen, ElementName=filterPopup, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" Margin="{StaticResource DialogItemsExceptLeftMargin}" FontFamily="Marlett" Content="6" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"/>
<Popup x:Name="filterPopup" PlacementTarget="{Binding ElementName=btnFilterPopup}" Placement="Bottom">
   <Border Background="{StaticResource ToolPopupBackgroundBrush}">
      <StackPanel Orientation="Vertical" Margin="{StaticResource DialogItemsMargin}">
         <CheckBox IsChecked="{Binding FilterCaseSensitive, Mode=TwoWay}" Margin="{StaticResource DialogItemsMargin}">Case sensitive</CheckBox>
         <CheckBox IsChecked="{Binding FilterExcludes, Mode=TwoWay}" Margin="{StaticResource DialogItemsExceptTopMargin}">Exclude matching</CheckBox>
      </StackPanel>
   </Border>
</Popup>
thatguy
  • 21,059
  • 6
  • 30
  • 40
  • In the end I simply configured the bindings manually from the code behind. What's interesting is that if you copy'n'paste my code directly to a window, it just works. But placed in the UserControl shown by the DataTemplate mechanisms, it did not. – Spook Jan 21 '21 at 11:52