Why is this basic binding failing with an exception? - c#

Can anyone explain why the binding on TagObject below code throws the following binding exception?
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Value; DataItem=null; target element is 'TagObject' (HashCode=37895910); target property is 'Value' (type 'String')
My suspicion is its because TagObject itself isn't a subclass of FrameworkElement so it doesn't have a data context itself and thus doesn't know how to resolve the XAML binding.
To test, I changed the base type of TagObject to FrameworkElement and sure enough, the binding error went away, but Value still didn't change. My theory there is although the binding was now valid, TagObject wasn't part of the Visual Tree, therefore it didn't inherit its DataContext.
I also tried giving 'TextBlocka name, then specifying it as theElementNamein the binding, but that again threw a binding exception. In this case, my suspicion is that it can't find the named element becauseTagObject` still is not part of the visual tree, even with the base-class change above.
For the record, I do know a solution would be to simply hide that object creation behind a ValueConverter to wrap it for me, but I'm wondering if there's a XAML-only solution to address that binding on TagObject.
Here's the XAML:
<Window x:Class="Test.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test">
<Window.Resources>
<DataTemplate DataType="{x:Type test:DataObject}">
<TextBlock Text="{Binding Value}">
<TextBlock.Tag>
<test:TagObject Value="{Binding Value}" />
</TextBlock.Tag>
</TextBlock>
</DataTemplate>
</Window.Resources>
<ListBox x:Name="MainListBox" BorderThickness="0" />
</Window>
This doesn't work either...
<TextBlock x:Name="MyTextBlock" Text="Test">
<TextBlock.Tag>
<test:TargetObject Value="{Binding DataContext.Value, ElementName=MyTextBlock}" />
</TextBlock.Tag>
</TextBlock>
Here's the code:
using System.Windows;
using System.Collections.ObjectModel;
namespace Test
{
public partial class TestWindow : Window
{
public TestWindow()
{
InitializeComponent();
var sourceItems = new ObservableCollection<DataObject>();
for(int i = 1; i <= 10; i++)
sourceItems.Add(new DataObject() { Value = "Item " + i});
MainListBox.ItemsSource = sourceItems;
}
}
public class DataObject : DependencyObject
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(string),
typeof(DataObject),
new UIPropertyMetadata(null));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
public class TagObject : DependencyObject
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(string),
typeof(TagObject),
new UIPropertyMetadata(null));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
}

I recreated your sample-code in a VS2013 solution and replicated what you're seeing. The binding seems to be happening just fine, but yes - it is spitting out those annoying error messages. After some research, it appears that this is a known bug -- I see others complaining of it as well. The binding is working fine. The problem, as far as I can tell from others, is that, for ItemsSources, WPF is trying to optimize the evaluation of the styles and data-templates as it composes the visual tree, sometimes doing this before the binding is available on the actual elements. In your case, the TextBlock is already available, and it has a binding value, but the Tag property within it is composed before that and thus complains of a missing FrameworkElement (which, an instant later, is no longer missing).
I do not find this to be an encouraging sign from the WPF team, as this seems like a very simple scenario. Correct code should never be emitting warnings or errors.

Related

How to implement a WinUI 3 UserControl with ComboBox, Enum and DependencyProperty

Suppose we have
public enum MyEnum {None, First, Second}
and in MainWindow.xaml.cs, we have
private IList<MyEnum> _myEnums = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().ToList();
public IList<MyEnum> MyEnums => _myEnums;
public MyEnum SelectedMyEnum {get;set;}
and in MainWindow.xaml we have
<StackPanel>
<ComboBox ItemsSource="{x:Bind MyEnums}" SelectedItem="{x:Bind SelectedMyEnum, Mode=TwoWay}"/>
</StackPanel>
This works, as expected.
Now suppose we want to replace ComboBox with a user control, i.e.
<local:ExampleControl MyEnums="{x:Bind MyEnums}" SelectedMyEnum="{x:Bind SelectedMyEnum, Mode=TwoWay}"/>
So, in ExampleControl.xaml we have
<StackPanel>
<ComboBox
ItemsSource="{x:Bind MyEnums}"
SelectedItem="{x:Bind SelectedMyEnum,Mode=TwoWay}">
</ComboBox>
</StackPanel>
and in ExampleControl.xaml.cs we have
// SelectedMyEnum
public static readonly DependencyProperty SelectedMyEnumProperty =
DependencyProperty.Register(
"SelectedMyEnum",
typeof(MyEnum),
typeof(ExampleControl),
new PropertyMetadata(MyEnum.None)); // (1)
public MyEnum SelectedMyEnum
{
get { return (MyEnum)GetValue(SelectedMyEnumProperty); }
set { SetValue(SelectedMyEnumProperty, value); } // (2)
}
// MyEnums
public static readonly DependencyProperty MyEnumsProperty =
DependencyProperty.Register(
"MyEnums",
typeof(IEnumerable<MyEnum>),
typeof(ExampleControl), null
);
public IEnumerable<MyEnum> MyEnums
{
get { return (IEnumerable<MyEnum>)GetValue(MyEnumsProperty); }
set { SetValue(MyEnumsProperty, value); }
}
But this doesn't work. On startup, the SelectedMyEnum setter (2) is called repeatedly with the value MyEnum.None until there is a stack overflow,
System.StackOverflowException
HResult=0x800703E9
Source=<Cannot evaluate the exception source>
StackTrace:
<Cannot evaluate the exception stack trace>
Instead of new PropertyMetadata(MyEnum.None) in (1), I tried
new PropertyMetadata(MyEnum.None, OnEnumChanged)
with
private static void OnEnumChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = (ExampleControl)obj;
MyEnum myVal = (MyEnum)args.NewValue;
}
but that made no difference, the function OnEnumChanged was called repeatedly, along with the setter, with myVal == MyEnum.None, until stack overflow.
I checked the WinUI Gallery for examples of dependency properties for enums, but couldn't find any, but there were plenty of examples for double, int, and bool, e.g. ItemHeight in WrapPanel, I think I'm doing this right.
I must be missing something, but can't see it. I've searched on ComboBox, DependencyProperty, Enum, and found some matches, e.g Enum as a DependencyProperty of a UserControl, but I didn't find them helpful. Any help is appreciated.
Environment:
Microsoft Visual Studio Community 2022
Version 17.1.0
VisualStudio.17.Release/17.1.0+32210.238
Microsoft .NET Framework
Version 4.8.04161
Answered here by Roy Li - MSFT on the Microsoft Q&A site. It's related to a WinUI defect in the x:Bind markup extension. In the user control, in the case of enums, x:Bind doesn't work with SelectedMyEnum, it needs to be written using Binding as
<ComboBox
ItemsSource="{x:Bind MyEnums}"
SelectedItem="{Binding SelectedMyEnum,Mode=TwoWay}">
</ComboBox>
and DataContext set to this. In my experiments, it appears to be only an issue with x:Bind and TwoWay binding, OneWay and OneTime bindings with x:Bind appear okay. And not an issue if binding to strings rather than enums.

UWP bugs? Binding to some nested DependencyProperty would not working

I'm developing a chart control which is derived from a UserControl, everything is fine until I found that a DependencyProperty of bottom axis can not be set through binding. Here is the code snippet of the chart control:
public class AxisBase : FrameworkElement
{
public IList<double> ExtraGridLines
{
get { return (IList<double>)GetValue(ExtraGridLinesProperty); }
set { SetValue(ExtraGridLinesProperty, value); }
}
public static readonly DependencyProperty ExtraGridLinesProperty =
DependencyProperty.Register("ExtraGridLines", typeof(IList<double>), typeof(AxisBase), new PropertyMetadata(null, OnExtraGridLineDataPropertyChanged));
private static void OnExtraGridLineDataPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Problem: data bing would not work, and
//this call back method will never get called!
Debug.WriteLine("ExtraGridLines changed...");
}
}
public sealed partial class MyChart : UserControl
{
public MyChart()
{
this.InitializeComponent();
}
public AxisBase BottomAxis
{
get { return (AxisBase)GetValue(BottomAxisProperty); }
set { SetValue(BottomAxisProperty, value); }
}
public static readonly DependencyProperty BottomAxisProperty =
DependencyProperty.Register("BottomAxis", typeof(AxisBase), typeof(MyChart), new PropertyMetadata(null));
}
and here is the binding code:
<Page x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
x:Name="rootPage">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:MyChart>
<local:MyChart.BottomAxis>
<local:AxisBase ExtraGridLines="{Binding MyExtraGridLines, ElementName=rootPage}" />
</local:MyChart.BottomAxis>
</local:MyChart>
</Grid>
</Page>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
private List<double> _myExtraGridLines;
public List<double> MyExtraGridLines
{
get { return _myExtraGridLines; }
set
{
_myExtraGridLines = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("MyExtraGridLines"));
}
}
public MainPage()
{
this.InitializeComponent();
this.MyExtraGridLines = new List<double>() { 10, 100, 1000 };
}
public event PropertyChangedEventHandler PropertyChanged;
}
The problem is: the binding seems never get worked, the PropertyChangedCallback method of dp ExtraGridLines is never called. However these code works in WPF. What's wrong with my code? or is it a bug?
Any idea would be appreciate!
Edit1:
This seems having nothing to do with data type, even I've changed the type from IList<double> to int, string or object, the problem is same. But if I change the type of AxisBase from FrameworkElement to DependencyObject, then the binding would work. But this solution is not acceptable, since DependencyObject has no Style.
Edit2:
Finally, I think I've found the reason why ElementName binding would not work: ElementName binding only works for elements in the same NameScope. If you're interested, for more information, please see these two good links:
https://stackoverflow.com/questions/18389118/how-does-binding-elementname-work-exactly and
http://www.cnblogs.com/idior/archive/2010/05/28/1746513.html
btw 1: I was wrong at the very first: these code would not work in WPF either. The binding would throw a runtime error:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=rootPage'. BindingExpression:Path=ShowAxisLabel; DataItem=null; target element is 'AxisBase' (Name=''); target property is 'IsLabelVisible' (type 'Boolean')
btw 2: To make DataContext binding work in the above code, AxisBase should listen to the change of the DataContext of MyChart, or be added to the Logical or Visual tree of MyChart explicitly. That's a bug of my code. But due to NameScope problem, there seems no normal or elegant way to make ElementName binding here.
Probably I should change the title of this question to: Why ElementName binding to element in UserControl would not work in UWP/WPF?
This is likely a bug with traditional binding in UWP; however, with the new x:Bind, the following code should be working as expected.
<local:AxisBase ExtraGridLines="{x:Bind MyExtraGridLines, Mode=OneWay}" />
Note this gives you better performance too. So you should always consider using this binding approach first.
Update
Looks like the real issue is related to ElementName binding to an ancestor. But if you use the normal MVVM approach by specifying the DataContext like this -
MyExtraGridLines = new List<double>() { 10, 100, 1000 };
DataContext = this;
And removing the ElementName binding with a normal one -
<local:AxisBase ExtraGridLines="{Binding MyExtraGridLines}" />
Also make sure the axis element is in the visual tree by adding -
<UserControl x:Class="xxx.MyChart">
<Grid>
<ContentPresenter Content="{x:Bind BottomAxis, Mode=OneWay}" />
</Grid>
</UserControl>
This should work too.

How to create a Dependency Property for Binding

I'm working on a "simple" case. I like to create a new custom control which implements a DependencyProperty. In the next step I like to create a binding for updating the properties in both directions. I've builded a simple sample for this case, but the binding doesn't seem to work. I've found a way for updating the DPControl's property by using the FrameworkPropertyMetadata, but I don't know whether it's also a good idea to use the OnPropertyChanged event.
HERE is my sample project:
My control contains simply a Label
<UserControl x:Class="WPF_MVVM_ListBoxMultiSelection.DPControl"
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:WPF_MVVM_ListBoxMultiSelection"
mc:Ignorable="d" Height="84.062" Width="159.641">
<Grid Margin="0,0,229,268">
<Label Content="TEST" x:Name="label" Margin="0,0,-221,-102"/>
</Grid>
</UserControl>
and implement a custom dependency property. Currently, I have also implemented the PropertyChanged method for the FramePropertyMetadata and set in this method the label's content, but I like to get it work in both directions.
public partial class DPControl : UserControl
{
public DPControl()
{
InitializeComponent();
}
public string MyCustomLabelContent
{
get { return (string)GetValue(MyCustomLabelContentProperty);}
set
{
SetValue(MyCustomLabelContentProperty, value);
}
}
private static void OnMyCustomLabelContentPropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
DPControl control = (DPControl)source;
control.label.Content = e.NewValue;
}
public static readonly DependencyProperty MyCustomLabelContentProperty = DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
OnMyCustomLabelContentPropertyChanged
)
);
I use this control simply in a Window by:
<local:DPControl MyCustomLabelContent="{Binding MyLabelContent, Mode=TwoWay}" Margin="72,201,286,34"/>
MyLabelContent is a property in the ViewModel, which has implemented also the INotifyPropertyChanged interface.
public class ViewModel_MainWindow:NotifyPropertyChanged
{
private string _myLabelContent;
public string MyLabelContent
{
get { return _myLabelContent; }
set { _myLabelContent = value;
RaisePropertyChanged();
}
}...
So how can I get it work: Using the binding feature with my new control on custom properties.
In your UserControl:
<Label
Content="{Binding MyCustomLabelContent, RelativeSource={RelativeSource AncestorType=UserControl}}"
x:Name="label" Margin="0,0,-221,-102"/>
And get rid of that property-changed callback. All you need is the Binding.
I like to get it work in both directions
To make the dependency property two-way by default:
public static readonly DependencyProperty MyCustomLabelContentProperty =
DependencyProperty.Register(
"MyCustomLabelContent",
typeof(string),
typeof(DPControl),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
);
I omitted the unnecessary property change handler.
It can't usefully be two-way now, because Label.Content can't generate its own value. If you want your UserControl to set the value in its codebehind, that's easy:
MyCustomLabelContent = "Some arbitrary value";
If you did the binding like I showed you, that will update the Label in the UserControl XAML as well as the viewmodel property bound to the UserControl's dependency property.
If you want the XAML to set it, you'll need to
Lastly, this:
Margin="0,0,-221,-102"
Is not a good way to do layout. WPF layout with Grid, StackPanel, etc. is much easier and more robust.

Property Change not showing in Visual Studio designer

Using VS2015 I'm adding some custom functionality to a TextBlock for a small app and, since I can't derive from TextBlock itself (it's sealed), I'm deriving from UserControl.
In my xaml file, I have
<TextBlock x:Name="innerText"/>
As the only element within the usercontrol.
In my code-behind, I have the following used for accessing the text:
public string Label
{
get { return innerText.Text; }
set {
if (value != innerText.Text)
{
innerText.Text = value;
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs("Label"));
}
}
}
This works great when I'm running my app. On other pages, I am able to add instances of the control and set the "Label" property correctly. Unfortunately, the value of the "Label" property doesn't carry through to the inner textbox within the designer itself.
How can I get the value to update in the designer? While not strictly necessary (as I said, at run-time it works fine), it would make layout in the designer much easier for me.
Update:
I also tried using a DependencyProperty, with the same issue. Run-time works great, design-time shows nothing.
public string Label
{
get { return GetValue(LabelProperty).ToString(); ; }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(AutoSizingText), new PropertyMetadata(string.Empty));
And then, in the xaml, I set the DataContext for the entire control:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
And tried to bind the Text value:
<TextBlock Text="{Binding Label}" />
I would recommend using a dependency property instead of relying on setting the innerText element's Text property. A dependency property will behave just like any other property on a control, including updating in design mode.
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(MyClassName), new PropertyMetadata(string.Empty));
And your XAML will look like this:
<UserControl x:Name="usr" ...>
...
<TextBlock Text="{Binding Label, ElementName=usr}" ... />
...
</UserControl>
Pro tip: Type propdp, then Tab, Tab to quickly create a dependency property.
Here's an example usage:
<local:MyUserControl Label="Le toucan has arrived"/>
Note: You do not need to set the DataContext to Self when using a dependency property, this will generally screw things up as the UserControl should not set it's own DataContext, the parent control should.

ValidationRules not removing error when value is set back to valid value

INTRODUCTION
I have created a DecimalTextBox UserControl that houses some decimal validation I need done, so that I dont need to recreate the validation each time, and can just use the UserControl instead. This validation has properties that need to be bound to, and so I have created DependencyProperties so I can bind to them, according to this article by Josh Smith.
THE PROBLEM
The control's validation is behaving strangely. When I type an erroneous value into the TextBox, it shows up as an error. However when I try to change the value back in the code the value shown in the textbox remains unchanged.
Here are the steps I perform that cause this error (in this example 1 is an invalid value):
Load the form and the default value is 0.
Enter 1 into the textbox (and the textbox goes red due to the validation result being and error)
In the code I set the property bound to the textbox to 0
The form still displays 1 in a red textbox
CODE EXAMPLE
I prepaired an example demonstrating the problem, which can be downloaded here.
I'll post some of the code here, if you want more let me know.
ValidationTestControl's XAML
<UserControl x:Class="WPFTestProject.ValidationTestControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:WPFTestProject"
x:Name="ValidationTest"
Height="50" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Type 'Banana' here: "></TextBlock>
<TextBox MinWidth="100">
<TextBox.Text>
<Binding ElementName="ValidationTest" Path="Text" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<v:NotBananaValidationRule>
<v:NotBananaValidationRule.NotWhatBinding>
<v:NotBananaBinding x:Name="NotBananaValidationBinding"></v:NotBananaBinding>
</v:NotBananaValidationRule.NotWhatBinding>
</v:NotBananaValidationRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text=" (the text will give error when = 'Banana')"></TextBlock>
</StackPanel>
</Grid>
ValidationTestControls Code Behind
(yes I know not very MVVM but I felt it was ok for this stand alone control)
public partial class ValidationTestControl : UserControl
{
public ValidationTestControl()
{
InitializeComponent();
Banana = "Banana";
Binding BananaBinding = new Binding("Banana");
BananaBinding.Source = this;
NotBananaValidationBinding.SetBinding(NotBananaBinding.NotWhatProperty, BananaBinding);
}
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());
public static DependencyProperty BananaProperty = DependencyProperty.Register("Banana", typeof(string), typeof(ValidationTestControl), new PropertyMetadata());
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public string Banana
{
get
{
return (string)GetValue(BananaProperty);
}
set
{
SetValue(BananaProperty, value);
}
}
}
ValidationRule and FrameWorkElement created for binding
public class NotBananaValidationRule:ValidationRule
{
private NotBananaBinding _notWhatBinding;
public NotBananaBinding NotWhatBinding
{
get { return _notWhatBinding; }
set { _notWhatBinding = value; }
}
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string what = value.ToString();
if(what == _notWhatBinding.NotWhat||string.IsNullOrEmpty(what))
return new ValidationResult(false,
"Please enter a string that is not " + _notWhatBinding.NotWhat);
else
return new ValidationResult(true, null);
}
}
public class NotBananaBinding : FrameworkElement
{
public static readonly DependencyProperty NotWhatProperty = DependencyProperty.Register(
"NotWhat", typeof(string), typeof(NotBananaBinding), new UIPropertyMetadata());
public string NotWhat
{
get { return (string)GetValue(NotWhatProperty); }
set { SetValue(NotWhatProperty, value); }
}
public NotBananaBinding() { }
}
Basically what this code does is check if you have typed "Banana" and then returns a validation error. The control exposes dependency properties because I want to be able to bind to them when I use the control. The FrameworkElement NotBananaBinding lets me create dependency properties (because it is a DependencyObject so i can bind stuff for the validation. The ValidationRule has a NotBananaBinding property that stores the dependency property and uses it in the validate method.
I know my property names are kinda crappy, sorry. The thing is that the example does a good job of displaying the error. In my haste to make an example I didn't name the variables very well. If you find the code crappy please download the sample here.
WHAT I'VE FIGURED OUT SO FAR
Basically this problem seems to be caused by the fact that I am not actually changing the value.
Even if I call OnPropertyChanged on the property, because the value is not different it doesn't try and reevaluate the Validation.
I can obviously change the value to some arbitrary Valid value and then change it to the one I want it to be and it will work, but I was hoping there is some way to get call validation manually, to reevaluate the value and then change it etc. The changing it away and back is kinda messy.
CONCLUSION
Am I doing something wrong (perhaps something about the way I implemented the validation and binding from Josh Smiths post)
Is this just a c# bug, or is the behavior intended? If so then why?
Are there any elegant ways to fix it?
u_u
The validation prevents the Text property to be set. Put a break point on the setter and you will see that it will not break when you type the last 'a'. If you type Bananan and hit Backspace and it errors, push the button and it will work. The validation makes sure that there can be no invalid value in your property. So if you save it to let's say a database while in error, it won't save an invalid value.

Categories