WPF FindAncestor binding finds source in Release, fails in Debug - wpf

I have a binding error that makes no sense. It always works in Release Mode. It sometimes -- only sometimes -- fails to find the source via FindAncestor in Debug Mode
Its for Style for a WPF Path that I use only when that Path is inside a specific custom control named LayerView. Below is the style. Note the 3 bindings that look for the parent source object LayerView are the ones that sometimes fail
<Style x:Key="LayerViewGuidePathStyle" TargetType="{x:Type Path}">
<Setter Property="Data">
<Setter.Value>
<MultiBinding Converter="{StaticResource CvtGuideOption}">
<Binding Source="{svc:ViewSettings}, Path=GuideOption}" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="ScanWidth" Converter="{ctrl:LogValueConverter}"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="ScanHeight" Converter="{ctrl:LogValueConverter}"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ctrl:LayerView}}" Path="SceneTransform" Converter="{ctrl:LogValueConverter}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
When they fail in Debug mode, this is the warning
System.Windows.Data Warning: 4 : Cannot find source for binding with
reference 'RelativeSource FindAncestor,
AncestorType='MyControls.LayerView', AncestorLevel='1''.
BindingExpression:Path=ScanHeight; DataItem=null; target element is
'Path' (Name='GuidePath'); target property is 'Data' (type 'Geometry')
Here is how I use this style inside my custom control LayerView
<ctrl:LayerView x:Name="MainLayerView" ItemsSource="{Binding Shapes}">
<ctrl:LayerView.Layers>
<Path x:Name="GuidePath" Style="{StaticResource LayerViewGuidePathStyle}" />
</ctrl:LayerView.Layers>
</ctrl:LayerView>
As you can probably tell, LayerView is an ItemsControl: Specifically a MultiSelector. My custom version merely adds non-dependency property named Layers which is a Collection<object>. The idea is that the user will set these statically in XAML, as I did above, and I will display them over the standard items of the control.
public class LayerView : MultiSelector
{
static LayerView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LayerView), new FrameworkPropertyMetadata(typeof(LayerView)));
}
private Collection<object>? _layers;
public Collection<object> Layers => _layers ??= new Collection<object>();
}
Finally, here is the ControlTemplate for LayerView. In addition to the required ItemsPresenter, I add an ItemsControl to show the contents of Layers as basically non-hit-detectable "overlays"
<ControlTemplate x:Key="LayerViewTemplate" TargetType="{x:Type gcl:LayerView}">
<Canvas x:Name="PART_MainCanvas" Background="Transparent">
<!-- We are an ItemsControl so present our items -->
<ItemsPresenter x:Name="PART_Items" />
<!-- Now present our custom layers. -->
<ItemsControl x:Name="PART_Layers"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Layers}"
IsHitTestVisible="False"/>
</Canvas>
</ControlTemplate
I have no moves here. I can usually fix bindings that fail, but I would expect consistent failure or success. Not a mix. And especially not intermittent failures It's almost as if it's a race condition.
Can anyone point me in the right direction as to why this would fail? Is my Layers property of the wrong type or is it missing some crucial attribute or something?

Without reproducing this error, of course, it is difficult to say exactly what the cause is. But I have some guesses.
The elements of the Layers collection are initialized BEFORE being added to the collection, but the binding is resolved after the element is loaded;
Layers is a regular CLR collection and information about loading ItemsControl (LayerView ) is not passed to it. Therefore, the elements of this collection, as I think, are initialized completely at once, including bindings. And this can happen before these items are included in the ItemsControl panel.
As I think, these errors also occur in Release, it's just that not all errors are caught in this mode. It is guaranteed that only errors that are critical for the operation of the application are caught. For example, exceptions.
At a minimum, you need to replace the collection with a FreezableCollection and implement a read-only DependecyProperty.
True, in this case, you can only include DependencyObject in it.
private static readonly DependencyPropertyKey LayersPropertyKey
= DependencyProperty.RegisterReadOnly(
nameof(Layers),
typeof(FreezableCollection<DependencyObject>),
typeof(LayerView),
new PropertyMetadata(null, (d, e) => ((LayerView)d).protectedLayers = (FreezableCollection<DependencyObject>)e.NewValue));
public static readonly DependencyProperty LayersProperty
= LayersPropertyKey.DependencyProperty;
public FreezableCollection<DependencyObject> Layers
{
get => (FreezableCollection<DependencyObject>)GetValue(LayersProperty);
}
// An optional auxiliary property for quickly obtaining a collection
// in the internal logic of the LayerView and its derivatives.
protected FreezableCollection<DependencyObject> protectedLayers {get; private set;}
// Initializing a Collection in a Constructor.
public LayerView() => SetValue(LayersPropertyKey, new FreezableCollection<DependencyObject>());
Supplement
Unfortunately, I could not immediately check your version - there was no "at hand" computer with the Studio.
Now I checked it.
I did not change the code of the LayerView element itself, I did not set the template in the style.
I got the Binding error only if the Element Template is not set.
<Window x:Class="Core2023.SO.Joe.BindingErrors.BindingErrorsWindow"
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:Core2023.SO.Joe.BindingErrors"
mc:Ignorable="d"
Title="BindingErrorsWindow" Height="150" Width="400">
<Window.Resources>
<ControlTemplate x:Key="LayerViewTemplate" TargetType="{x:Type local:LayerView}">
<ItemsControl x:Name="PART_Layers"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Layers}"/>
</ControlTemplate>
</Window.Resources>
<Grid>
<local:LayerView>
<local:LayerView.Layers>
<TextBlock Text="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type local:LayerView}}}"/>
</local:LayerView.Layers>
</local:LayerView>
</Grid>
</Window>
In order to accurately diagnose the cause of your problem, I need more complete code to reproduce the problem.

Related

Binding a DataGrid when already having a DataContext declared in a Window----WPF

I am running into an issue that I haven't been able to resolve and although I've looked at a few similar posts, I haven't found anything that explains my situation.
Basically I have a WPF Window:
<Window x:Class="NewGame">
<DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
In the class I implement INotifyPropertyChanged to utilize bindings for some properties I have set up to update dynamically using XAML. For instance, I have a DB that has Primary, Secondary and Trim Colors(Hex codes) listed for teams, and the properties automatically will update based on changing the team. So I have the BorderBrush, Foreground and Backgrounds on various things auto-updating in XAML using:
<Foreground="{Binding Path=MyPrimColor}">
<Background={Binding Path=MySecColor}">
<BorderBrush={Binding Path=MyTrimColor}">
, etc...each could be any of the properties, it doesn't matter, those are all working fine.
Now, I have a DataGrid which I need to bind to a DataTable to display the players on the team, and that is where I have run into the issue. It tells me the "Items Collection must be empty before using Itemsource" and throws an exception. This was never an issue until I started using the databindings in XAML, when I had things set in code behind, everything worked fine, but I also know this isn't the way things are supposed to be done, which is why I want to have it working with the XAML data-bindings.
I created MyDT a property as DataTable, and when I try to bind
<DataGrid DataContext="{Binding Path=MyDT}">, it causes the Foreground and Background binding paths to try to bind to the Data.DataTable object as well which obviously throws an error.
I have seen some say I need to use <DataGrid.DataContext> inside the <datagrid> but I haven't gotten that to work either. I understand where the problem is coming from---I already have the bindings set at a higher level, but I just don't know how to fix it in XAML by only binding the DataGrid to the DT property while leaving the others to bind to the class level.
Here is the full code section in XAML:
<DataGrid x:Name="TeamRosterDT"
Height="400"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Foreground="{Binding Path=MyTrimColor}"
RowBackground="{Binding Path=MySecColor}"
AlternatingRowBackground="{Binding Path=MyPrimColor}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
ColumnWidth="Auto"
HorizontalScrollBarVisibility="Visible"
ItemsSource="{Binding}"
Opacity="0.8"
VerticalScrollBarVisibility="Visible"
Visibility="Hidden"
DataContext="{Binding Path=MyDT}">
<DataGridColumnHeader Style="{StaticResource DataGridHeaderStyle}" />
<DataGridCell HorizontalContentAlignment="Center" />
</DataGrid>
no need to change DataContext (DataContext="{Binding Path=MyDT}"), only bind ItemsSource (<DataGrid ItemsSource="{Binding Path=MyDT}"> or <DataGrid ItemsSource="{Binding Path=MyDT.DefaultView}">)
Exception is thrown because of incorrect items declaration (lines with DataGridColumnHeader, DataGridCell). They are added to Items list which is not supported when ItemsSource is set
I got the Exceptions resolved(I believe) by using:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="{Binding Path=MyPrimColor}" />
<Setter Property="Foreground" Value="{Binding Path=MyTrimColor}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.CellStyle>
However, the DataTable does not update properly---when the XAML is initialized its an empty DB, which is then filled and filtered according to what team is selected. When a new team is selected, the DT is simply filtered, not refilled. However, since I suppose this technically isn't a "change" to the DT itself, it doesn't fire the OnPropertyChanged event. How can I get it to update properly using XAML triggers, or is than an event I can utilize when the DB refilters?
Is where I would use an ObservableCollection?

TemplatedParent Binding in Resources of ControlTemplate

here is my scenario: I have a control template for a custom control which is rather big. To encapsulate the code of one of the features of that custom control, I'd like to introduce a helper class (called Internals). This class has logic code and provides some properties, which should be used in Bindings within the ControlTemplate.
Therefore I need to create an instance of this class in XAML and bind the TemplatedParent to a dependency property of the Internals class. My problem now is a concrete binding to the object the ControlTemplate is applied on. I've created a little proof of concept:
MainWindow.xaml:
<Window x:Class="WpfProofOfConcept.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfProofOfConcept="clr-namespace:WpfProofOfConcept"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow"
Height="650"
Width="525">
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Grid.Resources>
<wpfProofOfConcept:Internals x:Key="InternalsKey"
Base="{Binding Path=., RelativeSource={RelativeSource TemplatedParent}}" />
</Grid.Resources>
<Grid.RenderTransform>
<RotateTransform Angle="20" />
</Grid.RenderTransform>
<!-- if uncommented, Binding to Base is working -->
<!--<wpfProofOfConcept:Internals Base="{Binding Path=., RelativeSource={RelativeSource TemplatedParent}}" />-->
<ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button>
<TextBlock>Some text</TextBlock>
</Button>
</Grid>
</Window>
Internals.cs:
public sealed class Internals : FrameworkElement
{
public static readonly DependencyProperty BaseProperty =
DependencyProperty.Register("Base", typeof(object), typeof(Internals), new PropertyMetadata((o, s) => { }));
public object Base
{
get { return (object)GetValue(BaseProperty); }
set { SetValue(BaseProperty, value); }
}
public Internals()
{
}
}
I need a reference to the concrete object the Template is applied on in my Internals object - thus the TemplatedParent binding. It does not work and I do not get any binding errors in the output.
The strange thing is that it works when the Internals object is created outside of the resources section (see commented lines in the XAML code). I do not
And one more thing thats keeps me confused: In Silverlight, binding to the TemplatedParent within a resource section works. This seems to be a WPF issue.
Do you have any ideas how to get this binding done? Or can you explain why TemplatedParent binding does not work in the resources section?
Thanks for all your hints!
It is possible to make Bindings from the Resources! But the object in resources has to be a freezable object, e.g. its class has to extend Freezable. For more information, have a look at this link from Dr. WPF http://drwpf.com/blog/2008/05/22/leveraging-freezables-to-provide-an-inheritance-context-for-bindings/:
In addition to the above scenario (where a freezable is set as a
dependency property value on a dependency object), it is this enhanced
notion of an inheritance tree and inheritance context that allows
bindings on brushes, animations, and other freezable objects to work
when those objects are placed within a resource dictionary.
You have to change the class implementation to:
public sealed class Internals : Freezable
Grüße,
Peter ;)
When you are creating Internals as Resource, it is not the part of the VisualTree and hence binding is not getting applied to it
Once your define it inside the Grid, it become the part of VisualTree.

Using converter in Validation.Errors binding

In my WPF application (.Net 4.5) I want to provide extended visual feedback about validation results to the UI. The data layer's validation engine returns warnings and errors via INotifyDataErrorInfo interface.
I have the following XAML to display red or orange border depending on the error type and the list of error messages. Here errorToColor is a resource key of the value converter which returns red brush if there is at least one error in the Validation.Errors collection and orange brush if there are only warnings.
<TextBox Name="MappingName" Text="{Binding Path=Mapping.Name, NotifyOnValidationError=True}" >
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="{Binding Converter={StaticResource errorsToColor}}" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
<ListView DisplayMemberPath="ErrorContent" ItemsSource="{Binding}" />
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Now let's see what happens when I type some 'not valid' text in the TextBox.
Typed 'Text1' and changed focus.Debugger stepped into the converter and both validators resulting in two items in the ListView (1 error and 1 warning) and a red border. [OK]
Typed 'Text' to correct the error, changed focus.
The value converter wasn't even hit! Of course, the same red border. But the ListView has changed and shows only one warning.
Can somebody explain what's going on? Why the ListView receives collection change notification and the Border not? Is it because ListView is an ItemsControl and the Validation.Errors is wrapped into the CollectionView?
For those who are interested. The errorsToColor converter wasn't fired because the Validation.Errors collection didn't raise PropertyChanged event (needed trigger binding coverter) when errors were added or removed.
In order to raise PropertyChanged event we need to bind to a property which is changed on each error is added, for example Count. I still need the Errors collection itself in the converter, so I used a multi-binding here.
<Border BorderThickness="1">
<Border.BorderBrush>
<MultiBinding Converter="{StaticResource errorsToColor}">
<Binding Path="." />
<Binding Path=".Count" />
</MultiBinding>
</Border.BorderBrush>
<AdornedElementPlaceholder Name="adornedElement" />
</Border>
Now the errorsToColor converter (which is now implements IMultiValueConverter) is executed every time new error is added / removed.

How to bind to parent position changes in WPF using XAML and no code behind

I have a visio-like interfact but have actual model data behind some of the elements. The elements can be moved by the user.
I use a contentcontrol on a canvas whereby the viewmodels of the elements are places in the content which can then be displayed differently depending on their type but using the same contentcontrol. It is simple to bind the view to the different properties in the viewmodel. However, I have to save the position in the model, and I cannot find a binding solution.
1) The Application.Save Command is handled in the main view model, so I do not have access to the view there. That means I must save the postion data when the elements are moved, or is there a better approach?
2) Assuming that I am right with 1), I am looking to avoid code behind, i.e. I do not want the contentcontrol to deal with the elements that they have in their content. However, so far the code behind version is all I could come up with:
My code behind solution so far:
All model elements implement an interface:
public interface IViewElement
{
String Position { get; set; }
}
And in the contentcontrol:
void ContentControl_LayoutUpdated(object sender, EventArgs e)
{
IViewElement content = this.Content as IViewElement;
content.Position = new Point(Diagram.GetLeft(this), Diagram.GetTop(this)).ToString();
}
The XAML:
<Style TargetType="{x:Type diagram:Item}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type diagram:Item}">
<Grid Canvas.Top="{Binding ElementName=PART_ContentPresenter, Path=Content.Position, Mode=TwoWay}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{x:Null}">
<!-- PART_ContentPresenter -->
<ContentPresenter x:Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<DataTemplate DataType="{x:Type local:ViewModel}">
<StackPanel>
...
</StackPanel>
Just encapsulate the codebehind you've used in a Behavior
Why are you using a string to store the position? Use either a Point or two decimal values, and then bind your ContentControl's Canvas.Top and Canvas.Left position to these values using two-way binding.
It will automatically update the model when the Top and Left positions change.
Edit:
Here's an example:
<ContentControl Canvas.Top="{Binding ContentModel.Top, Mode=TwoWay}"
Canvas.Left="{Binding ContentModel.Left, Mode=TwoWay}"
Content="{Binding ContentModel}" />

WPF: Menu Items only bind command parameters once

Ive noticed this a couple of times when using menus with commands, they are not very dynamic, check this out. I am creating a menu from a collection of colours, I use it to colour a column in a datagrid. Anyway when i first bring up the menu (its a context menu) the command parameter binding happens and it binds to the column that the context menu was opened on. However the next time i bring it up it seems wpf caches the menu and it doesnt rebind the command parameter. so i can set the colour only on the initial column that the context menu appeared on.
I have got around this situation in the past by making the menu totally dynamic and destroying the collection when the menu closed and forcing a rebuild the next time it opened, i dont like this hack. anyone got a better way?
<MenuItem
Header="Colour"
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColumnColourCollection}"
ItemTemplate="{StaticResource colourHeader}" >
<MenuItem.Icon>
<Image
Source="{StaticResource ColumnShowIcon16}" />
</MenuItem.Icon>
<MenuItem.ItemContainerStyle>
<Style
TargetType="MenuItem"
BasedOn="{StaticResource systemMenuItemStyle}">
<!--Warning dont change the order of the following two setters
otherwise the command parameter gets set after the command fires,
not mush use eh?-->
<Setter
Property="CommandParameter">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<local:ColumnAndColourMultiConverter/>
</MultiBinding.Converter>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type DataGridColumnHeader}}" Path="Column"/>
<Binding Path="."/>
</MultiBinding>
</Setter.Value>
</Setter>
<Setter
Property="Command"
Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:ResultEditorGrid}}, Path=ColourColumnCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The problem is that ContextMenu's are apparently the root of their own visual tree I read somewhere that it takes the datacontext its parent, but only once on loading, so if the parents datacontext changes the menuitems does not. (unfortunately I can't find a link for that right not)
I have encountered this problem before, and what I did was use Josh Smith's Virtual Branch Pattern. It's fairly technical but the article helped me understand really well what was going on with this visual tree nonsense.
Essentially you create this bridge that binds to the view's datacontext. The bridge is created as a static resource, allowing you to bind to it from the context menu even if it is outside the visual tree.
Add this to your xaml:
<Window.Resources>
<!-- This is the "root node" in the virtual branch
attached to the logical tree. It has its
DataContext set by the Binding applied to the
Window's DataContext property. -->
<FrameworkElement x:Key="DataContextBridge" />
</Window.Resources>
<Window.DataContext>
<!-- This Binding sets the DataContext on the "root node"
of the virtual logical tree branch. This Binding
must be applied to the DataContext of the element
which is actually assigned the data context value. -->
<Binding
Mode="OneWayToSource"
Path="DataContext"
Source="{StaticResource DataContextBridge}"
/>
</Window.DataContext>
This is the bridge I spoke of. It takes the datacontext and __pushes it_ to to the bridges datacontext, which is (as I said before) a static resource.
Then you simply this to the contextmenu's datacontext:
DataContext="{Binding
Source={StaticResource DataContextBridge},
Path=DataContext}"
Now throw away all the relative pathing etc and use regular binding inside the menu items, and you should be fine. The datacontext will update as usual.
Just one note:
You will obviously have to have some property in the datacontext to discern which command to use, but i'm sure you can figure it out. This solution just deals with the way contextmenu's dont update

Resources