Set NotifyOnTargetUpdated for existing binding - c#

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);
}

Related

Is there a way to get UpdateSourceTrigger=PropertyChanged for compiled bindings?

I'm working on a UWP application and I realized that the default UpdateSourceTrigger mode for the TextBox control, which is LostFocus, can't be changed when using a compiled binding.
This means that whenever I want the binding to update for a TextBox, I have to use all this repeated boilerplate:
<TextBox
Text="{x:Bind ViewModel.Title, Mode=TwoWay}"
TextChanged="TextBox_OnTextChanged"/>
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.Title = ((TextBox)sender).Text;
}
Now, this is not too bad, but having to remember to create the TextChanged handler every single time a TextBox is used is annoying and error prone.
This would work fine with a classic binding:
<TextBox Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
But of course, here there would be the additional overhead of usinc classic bindings (involving runtime reflections, etc.).
Is there a way to get the same behaviour of UpdateSourceTrigger=PropertyChanged as well? I'd be completely fine with, say, writing a custom attached property that sets things up, as long as I can do everything I need directly from XAML, with no code behind involved.
Thanks!
UPDATE: (in response to Nico Zhu - MSFT's answer)
For my testing, it works well.
It doesn't for me, at all, As I said multiple times already, using UpdateSourceTrigger with x:Bind is just not possible. It doesn't compile, the property is shown in red in the XAML editor, it just isn't there. I really don't know where are you trying that, if you say it's working for you. I'm currently targeting 17763 as minimum and I can 100% guarantee that that does not work.
Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding.
I'm well aware of the difference, I've already mentioned this multiple times, both in my original question here (with code snippets too) as well as in my comments.
It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes
As I said, I'm aware of this too. But again, as from this question, this isn't the problem here at all. The issue is not with updates from the viewmodel to the bound property, but from the bound property (TextBox.Text in this case) to the viewmodel.
{x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
I'm sorry, but I have to say at this point I'm starting to wonder if you've actually read my initial question at all. I'm aware of this, and in fact you can see in both my original code snippets that I had already used the explicit Mode=TwoWay property in both my bindings.
And once again, this was not what the question was about, at all.
To reiterate: the issue here is that the TextBox.Text property defaults to the LostFocus trigger, and that the UpdateSourceTrigger property is not available for compiled bindings. So I'd like to know if there's a way to achieve the same, with a compiled binding, in XAML-only, without having to manually create a TextChanged handler every single time (and if not, if you plan to eventually add the UpdateSourceTrigger property to compiled bindings too).
Side note: I didn't mean to sound disrespectful here, and I hope we've now solved the existing misunderstandings with my question.
UPDATE #2: turns out the issue was causing by the ReSharper plugin, which was marking the UpdateSourceTrigger property as error in compiled bindings.
I've opened an issue for that here: https://youtrack.jetbrains.com/issue/RSRP-474438
Please check UpdateSourceTrigger documentation.
The default UpdateSourceTrigger value is Default. And
using default behavior from the dependency property that uses the binding. In Windows Runtime, this evaluates the same as a value with PropertyChanged. If you used Text="{x:Bind ViewModel.Title, Mode=TwoWay}", the Title will be changed when text changes. we have not need modify the viewmode in TextChanged even handler.
The premise is that we need implement INotifyPropertyChanged like the follow.
public class HostViewModel : INotifyPropertyChanged
{
private string nextButtonText;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText
{
get { return this.nextButtonText; }
set
{
this.nextButtonText = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For more detail please refer Data binding in depth document.
Update
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> doesn't compile at all, as I said the UpdateSourceTrigger property isn't available at all when using a compiled binding.
For my testing, it works well. Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding. It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes but {x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
Xaml
<StackPanel Orientation="Vertical">
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{x:Bind Title, Mode=OneWay}" /> <!--declare bind mode-->
</StackPanel>
Code behind
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _title;
public string Title
{
get
{
return _title;
}
set
{
_title = value;
OnPropertyChanged();
}
}

WPF Binding to UserControl´s DependencyProperty not working as expected [duplicate]

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.

Attached property: Check binding

Short question
Is there a quick way of knowing what a particular attached property is bound to, at runtime?
Detail
I'm debugging a UserControl (that inherits ItemsControl) which binds Canvas.Left and Canvas.Top of its items to two properties of the ViewModel objects, through a style. At runtime, I place a breakpoint at a certain location and want to inspect the binding of Canvas.Left attached property.
Note that I do not want to see the current value of the attached property for an item. I can easily do that by inspecting the value of Canvas.GetLeft(myItem) in the QuickWatch or Immediate windows. I want to check the actual binding here, i.e. the VM property name to which this attached property is bound for myItem.
I have tried Snoop already, which unfortunately doesn't show bindings of attached properties (if I didn't miss something obvious).
Is there a quick way of knowing what a particular attached property is bound to, at runtime
Yes, just override attached property somewhere (in your window?):
<Window x:Class="WpfApplication1.MainWindow" ... >
<Grid Canvas.Top="123"/>
</Window>
and code
public MainWindow()
{
InitializeComponent();
Canvas.TopProperty.OverrideMetadata(typeof(MainWindow), new FrameworkPropertyMetadata((d, e) =>
{
// you will get here for each Canvas.Top set in MainWindow
MessageBox.Show(d.ToString());
}));
}
You can get the attached property binding programmatically the same way as you get normal dependency property binding. I.e. from code behind to get the Canvas.LeftProperty attached property binding of the control with the name myItemsControl:
BindingExpression bindingExpression = myItemsControl.GetBindingExpression(Canvas.LeftProperty);
Binding parentBinding = bindingExpression.ParentBinding;

WPF Binding not updating with UpdateSourceTrigger=PropertyChanged

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.

Custom UserControl "IsEnabled" data binding not working

I have a kinda awful problem with my WPF application right now...
I have a custom UserControl used to edit details of a component. It should start by being not enabled, and become enabled as soon as the user chose a component to edit.
The problem is: the IsEnabled property does not even change.
Here is my code:
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled}"
DataContext="{Binding VmComponent}" />
EditorEnabled is a property in my ViewModel (VmComponent), and is by default false, becomes true when the user chose a component or created one
Just for the record, in my ViewModel:
private Boolean _editorEnabled = false;
public Boolean EditorEnabled
{
get { return _editorEnabled; }
set
{
_editorEnabled = value;
OnPropertyChanged("EditorEnabled");
}
}
When I try to launch my app, the UserControl is starting... enabled.
I added breakpoints everywhere, the EditorEnabled is false from the beginning.
I also did a horribly stupid thing to try to figure out what's happening: I created a converter (so useful -- converting a boolean to boolean -- eh), put a breakpoint on it, and... The code is never reached.
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled, Converter={StaticResource BoolConverter}}"
DataContext="{Binding VmComponent}" />
That probably means that the property isEnabled is never set, since the converter is never reached.
Do you see any kind of problem there? I started working in WPF about one week ago and therefore I may have missed something essential...
Thank you very much for your time :-)
You should add a DependencyProperty for the binding to work properly. See here for more information.
Code-behind:
public static readonly DependencyProperty EditorEnabledDependencyProperty = DependencyProperty.Register("EditorEnabled", typeof(bool), typeof(UcComponentEditor), new PropertyMetadata(false));
public bool EditorEnabled
{
get { return (bool)base.GetValue(UcComponentEditor.EditorEnabledDependencyProperty); }
set { base.SetValue(UcComponentEditor.EditorEnabledDependencyProperty, value); }
}
The issue I think is that there is a binding on the DataContext property of the user control. Which means the EditorEnabled property should be a property in the VmComponent object. At least that's what my problem was.
To get around it, I specified a proper source to the binding of IsEnabled. Once I did that the control started working as expected.
Hope that helps.
Encapsulating your control in a DockPanel (for example) will remove the need for a DependencyProperty.
You can then simply do your binding with the dockpanel instead of the custom control. Setting the variable bound to IsEnabled on the Dockpanel will automatically enable or disable the items contained in the Dockpanel.

Categories