In .NET 4.0, Run.Text is bindable. So I tried to bind it:
<Run Text="{Binding DisplayText}"/>
But when I ran, I got an error: "A TwoWay or OneWayToSource binding cannot work on the read-only property 'DisplayText' of type 'SomeNamespace.SomeClass'."
My DisplayText property was indeed read-only, but so is a Run -- Runs go in in TextBlocks, which you can't edit. So why would I be getting this error? I dug into PresentationFramework with dotPeek and sure enough:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof (string), typeof (Run),
(PropertyMetadata) new FrameworkPropertyMetadata((object) string.Empty,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(Run.OnTextPropertyChanged),
new CoerceValueCallback(Run.CoerceText)));
The fourth line, plain as day, specifies that Run.Text should bind two-way by default, which makes no sense and seems like a glaring design bug.
Of course, this is easy enough to work around:
<Run Text="{Binding DisplayText, Mode=OneWay}"/>
But why should I have to work around it? Why does Run bind two-way by default?
Just a guess here:
It might be because Run objects are also used in the RichTextBox control, and I can imagine this control might want to Bind TwoWay by default!
Related
I have a binding in xaml <TextBlock Style="{StaticResource textStyle}" Text="{Binding DisplayText}" />.
I am attempting to write an attached behavior that reacts to the bound DisplayText value changing. If I specify NotifyOnTargetUpdated=True in the xaml, I can react to the change within the behavior and everything is fine, but I'd rather not depend on binding the Text property in a specific way just to make the behavior work.
My thought was to change the NotifyOnTargetUpdated value on the existing TextBlock.TextProperty binding when the behavior is opted in. I am using the below code to do so, where tb is the TextBlock being opted in.
var textBinding = BindingOperations.GetBinding(tb, TextBlock.TextProperty);
textBinding.NotifyOnTargetUpdated = true;
tb.SetBinding(TextBlock.TextProperty, textBinding);
The behavior is opted in like so, in the style:
<Setter Property="behaviors:Text.AutoSizeText" Value="True"/>
Initially this didn't work because textBinding was null. I can get around this by binding the Text property in xaml before the behavior property, but this still leaves an external dependency that I don't like (xaml ordering). If I do go this route, I get the below exception, which seems to indicate that I can't accomplish this in this way, at all.
InvalidOperationException: Binding cannot be changed after it has been used.
So then, how can I go about automatically handling setting NotifyOnTargetUpdated for the Text binding when the behavior is opted in?
I was able to solve my problem thanks to direction provided by #canton7. I was originally (as is often the case) looking for the way to implement my imagined solution, rather than a solution that fit my need. After adjusting my outlook, my working solution is thus:
Add the AttachedProperty InternalText to the behavior class, with a property changed handler.
private static readonly DependencyProperty InternalTextProperty = DependencyProperty.RegisterAttached(
"InternalText", typeof(string), typeof(Text), new PropertyMetadata(default(string), HandleInternalTextChanged));
In the changed handler (HandleInternalTextChanged above) do the work that I would have done in a TargetUpdated handler if my original idea to set NotifyOnTargetUpdated had worked out.
On opt-in to my behavior, create a binding from the opted-in TextBlock.Text to the InternalText attached property.
var internalBinding = new Binding { Source = tb, Path = new PropertyPath(TextBlock.TextProperty) };
tb.SetBinding(InternalTextProperty, internalBinding);
The HandleInternalTextChanged callback on InternalTextProperty allows me to work around being unable to change the NotifyOnTargetUpdated value by providing an alternate means of notifying on each change.
I prefer to bind internally to DisplayText because I'd prefer to bind to the source, if possible, rather than daisy-chain through the TextBlock.Text property.
If you need a binding to a source property, then it is created in a slightly different way.
var binding = BindingOperations.GetBindingBase(tb, TextBox.TextProperty);
if (binding == null)
{
tb.ClearValue(InternalTextProperty);
}
else
{
tb.SetBinding(InternalTextProperty, binding);
}
This question already has answers here:
How to implement two-way binding on a property?
(2 answers)
Closed 6 years ago.
I have some strange problem with DependecyProperty-binding.
To keep the question simpler i´ve created some dummy-control, that has the same unwanted behaviour
I have a UserControl, that has a DependencyProperty defined in code behind:
public static readonly DependencyProperty TestValueProperty = DependencyProperty.Register("TestValue", typeof(string), typeof(Test), new PropertyMetadata(default(string)));
public string TestValue
{
get { return (string)GetValue(TestValueProperty); }
set { SetValue(TestValueProperty, value); }
}
This property is used in XAML:
<Label Content="{Binding TestValue}" />
This control should be used in another control like this:
<views:Test TestValue="{Binding Settings.Setting123}" />
Settings is defined in viewmodel as property.
But the content of Settings.Setting123 is not visible in my usercontrol´s label.
When writing some fixes value instead of the binding it works fine:
<views:Test TestValue="Test" />
But of course i do not want a fixed value, but the content of the bound object.
Any hint what is going wrong here?
Thanks in advance!
You didn't share enough code for anybody to recreate the issue, but reading between the lines, I'm guessing that Label is in your UserControl XAML. If TestValue is a property of your UserControl, this will probably work:
<Label Content="{Binding TestValue, RelativeSource={RelativeSource AncestorType=UserControl}}" />
However, one reason you might have done that (and had it semi-work, with literal strings) is if you made your UserControl its own DataContext. In that case, then the problem is that you made your UserControl its own DataContext. If you did that, that Binding on the bound one is being evaluated in the context of the UserControl, which does not have a Settings.Setting123 property.
What a control's DataContext means, is that when you have a Binding on one of the controls properties or inside its XAML, that's where the Binding goes to look for the property you bind to. You're explicitly telling it to look in the wrong place.
If you make your UserControl its own DataContext, you can't bind anything to it. That's why you shouldn't do that. It's like one of those machines that does nothing but unplug itself from the wall. Instead, use {RelativeSource AncestorType=UserControl} bindings as above inside the UserControl XAML.
I shouldn't have to guess. You claim you created a minimal verifiable example, but didn't bother sharing it. If you share it, we can solve your problem with confidence.
I have a TabControl in which I set the DataContext to an instance of the this class, It's basicly a wrapper for DependencyProperties of a static class with the same properties.
In my Markup I set the DataContext like this
<TabControl DataContext="{Binding ElementName=self, Path=Settings}">
and binding to the property within the TabControl like this
<TextBox Text="{Binding Path=Url, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
However, this does not lead to any updates of the source when the content of the TextBox is changed. I can change the content of the TextBox, let it loose focus etc. it does just not update the source.
Url is a dependency property and when set from XAML, wrapper property setter won't be called.
From MSDN:
The current WPF implementation of its XAML processor is inherently
dependency property aware. The WPF XAML processor uses property system
methods for dependency properties when loading binary XAML and
processing attributes that are dependency properties. This effectively
bypasses the property wrappers. When you implement custom dependency
properties, you must account for this behavior and should avoid
placing any other code in your property wrapper other than the
property system methods GetValue and SetValue.
In case you want to do something on its property changed you should provide PropertyChangedCallback and write code there.
You can refer to the sample here in case PropertyChangedCallback is new to you. Something like:
public static readonly DependencyProperty UrlProperty =
DependencyProperty.Register(
"Url",
typeof(string),
typeof(SettingsWrapper),
new PropertyMetadata(OnUrlChanged)
)
);
private static void OnUrlChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
SettingsWrapper instance = (SettingsWrapper)d;
instance.Settings.Url = e.NewValue.ToString();
}
You said in a (now deleted) comment that your Window has x:Name="self", however the Window class does not have a property called Settings.
If this is an attached property, you need to reference it by the attached property by the full name, and wrap it in parenthesis.
For example,
<TabControl DataContext="{Binding ElementName=self, Path=(local:MyClass.Settings)}">
See WPF Attached Property Data Binding for more info.
I'm trying to bind to a value, run a converter over it, and then display a property of that value. Having the Converter directly return the property I want wouldn't work, as I need property changes to be tracked.
What I'm trying to achieve would be something like this:
// NOTE: FOLLOWING IS NOT SUPPORTED BY WPF
// A 'Binding' cannot be set on the 'Source' property of type 'Binding'.
// A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Text={Binding TextField Source={Binding SomeValue, Converter={StaticResource GetObjectFromValueConverter}}}`
Ideally, this would all be wrapped up in a simple markup extension.
Text={l:GetTextField SomeValue}
Problem is, I haven't been able to find any way to do this other than bind the Tag of the element to the converter, and then bind the target field to the property as follows:
Tag={Binding SomeValue, Converter={StaticResource GetObjectFromValueConverter}}
Text={Binding Tag.TextField, RelativeSource={RelativeSource Self}}
This is obviously cumbersome, limited (you only get one Tag field) and feels abusive. How else can I go about achieving what I want whilst monitoring for changes of TextField though?
You can bind the DataContext of the TextBox, instead of the Tag. That will make your other bindings much simpler:
DataContext="{Binding SomeValue, Converter={StaticResource GetObjectFromValueConverter}}"
Text="{Binding TextField}"
This assumes that you do not have any other bindings on the TextBox that require the inherited DataContext. For example, in the bindings below Text2 would be broken:
DataContext="{Binding SomeValue, Converter={StaticResource GetObjectFromValueConverter}}"
Text2="{Binding SomeOtherValue, Converter={StaticResource GetObjectFromValueConverter}}"
Text="{Binding TextField}"
Also, if you have a more complex control other than TextBox, the DataContext for any controls below it in the logical/visual tree will also be affected.
I've created a custom user control named MyCustomComboBox. Everywhere in the application I put it I do the following:
<Widgets:MyCustomComboBox
Foo="{Binding Foo,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
MyCustomComboxBox has the dependency property Foo, I have some validation and other logic in the combobox which is the very reason why I wrapped it up in a custom control.
The custom combobox is included another user control which also has a Foo property, which the combobox's is bound to.
But I also have to set UpdateSourceTrigger and Mode, I would like to somehow specify that those are the default values when binding to that DependencyProperty. Can it be done?
The default BindingMode can be specified in the dependency property metadata:
public static readonly DependencyProperty FooProperty = DependencyProperty.Register(
"Foo",
typeof(string),
typeof(MyCustomComboBox),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
However, to my knowledge there is no way to provide a default for the update source trigger.