Page 1 of 1

Binding to a UserControl's property which is bound to VM

Posted: 23 Jul 2021, 17:43
by yuyoyuppe
Hi folks, I'm investigating bindings mechanics rn and don't understand why it doesn't play nicely with UserControls. Here's my simplified code:


My View:
<Grid DataContext="{Binding TheVM}">
  <!-- THIS WORKS -->
  <!-- <TextBox Text="{Binding Scale}" /> -->

  <!-- THIS DOESN"T WORK -->
  <d:LabeledInputBox Text="{Binding Scale}" />
</Grid>
LabeledInputBox UserControl is just a TextBox with some properties and visual stuff attached:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:d="clr-namespace:D"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="D.LabeledInputBox">
  <Grid>
   <!-- ...other stuff -->
  <TextBox Text="{Binding Text, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type d:LabeledInputBox}}}}" />
  </Grid>
</UserControl>
NS_IMPLEMENT_REFLECTION(LabeledInputBox, "D.LabeledInputBox")
{
    Noesis::DependencyData* data = NsMeta<Noesis::DependencyData>(Noesis::TypeOf<SelfClass>());
    data->RegisterProperty<Noesis::String>(TextProperty,
                                           "Text",
                                           Noesis::PropertyMetadata::Create(Noesis::String{}));
}
TheVM ViewModel class doesn't have anything fancy as well:
NS_IMPLEMENT_REFLECTION(TheVM, "D.TheVM") // inherited from Noesis::DependencyObject
{
    Noesis::DependencyData* data = NsMeta<Noesis::DependencyData>(Noesis::TypeOf<SelfClass>());
    data->RegisterProperty<Noesis::String>(
        ScaleProperty, "Scale", Noesis::PropertyMetadata::Create(Noesis::String{}));
}
If it's relevant, TheVM is stored in a parent VM which is inherited from NotifyPropertyChangedBase and declared like this:
    NsProp("TheVM", &ParentVM::GetTheVM);
Now, I'm continuously polling the Scale property value on TheVM object from code while typing into TextBox/d:LabeledInputBox. The property gets updated properly when TextBox is used, but not with d:LabeledInputBox, although it gets the default Scale value if I set one. What am I doing wrong?

Re: Binding to a UserControl's property which is bound to VM

Posted: 23 Jul 2021, 18:44
by sfernandez
TextBox's Text property is defined to bind TwoWay by default:
    data->RegisterProperty<String>(TextProperty, "Text",
        FrameworkPropertyMetadata::Create(String(),
            FrameworkPropertyMetadataOptions_BindsTwoWayByDefault,
            UpdateSourceTrigger_LostFocus));
You can define your UserControl's Text property the same way, or explicitly set the mode in the binding:
<Grid DataContext="{Binding TheVM}">
  <d:LabeledInputBox Text="{Binding Scale, Mode=TwoWay}" />
</Grid>
If you don't do that updates only go from the VM's property to the UserControl's property.

Re: Binding to a UserControl's property which is bound to VM

Posted: 24 Jul 2021, 00:21
by yuyoyuppe
Thanks, that did the trick.

I've also spent an unnecessarily big amount of time fiddling with setting DataContext properties to get all my bindings work with deeply nested VMs etc. Usually a binding would just fail silently without any logging, perhaps that's due to some of them being DataTemplates (so different tree or something?). Is there something similar to PresentationTraceSources.TraceLevel=High in Noesis?

Re: Binding to a UserControl's property which is bound to VM

Posted: 26 Jul 2021, 17:04
by jsantos
There is extra binding verbosity in the "Binding" channel available in the logging callback. By default, our application framework only enables that information when using "--log_binding".
    SetLogHandler([](const char*, uint32_t, uint32_t level, const char* channel, const char* msg)
    {
        // By default only global channel is dumped
        bool filter = !StrIsNullOrEmpty(channel);

        // Enable "Binding" channel by command line
        if (gCommandLine.HasOption("log_binding"))
        {
            if (StrEquals(channel, "Binding"))
            {
                filter = false;
            }
        }
Please, let me know if this gives you enough information, it should be similar to WPF high tracing level.

Re: Binding to a UserControl's property which is bound to VM

Posted: 04 Aug 2021, 11:01
by yuyoyuppe
Sorry for the late reply. We do not use the application framework and our callback is set to log everything. So I guess I've already seen some output from that channel, but there were definitely cases with silent failures. At the moment, all of those issues are fixed, and I couldn't reproduce any trying things from the top of my head. I'll update this thread if something comes up. Thank you for your support!

Re: Binding to a UserControl's property which is bound to VM

Posted: 19 Aug 2021, 00:02
by yuyoyuppe
Ok, I've just stumbled upon a case when the binding silently fails:
    <UserControl.Resources>
        <Style TargetType="{x:Type ContentControl}">
            <Style.Triggers>
                <DataTrigger ...>
                    <Setter Property="ContentTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <Grid>
                                    <Control x:Name="Something">
                                        <Control.RenderTransform>
                                            <!-- silently fails to bind -->
                                            <TranslateTransform X="{Binding SomeProp}"/>
                                            <!-- works ok! -->
                                            <TranslateTransform Y="{Binding SomeProp, ElementName=DataContextName}"/>
                                        </Control.RenderTransform>
                                    </Control>
...
As you can see, since Something Control is not in the visual tree, you should e.g. specify its DataContext via ElementName, so Y property will be bound properly, while X will silently fail.

Re: Binding to a UserControl's property which is bound to VM

Posted: 19 Aug 2021, 14:06
by sfernandez
The problem in your xaml is not with the Binding, is that you are assigning 2 TranslateTransforms to the RenderTransform property.
When that xaml gets loaded it shows the following parsing error:
[NOESIS/E] BindingTest.xaml(15): Duplicate assignment of property 'UIElement.RenderTransform' in 'Control'.
And the first TranslateTransform is simply ignored, only the second one is set. That is why you don't get any message from the first binding when the template gets applied.

If I change the xaml to this:
<Control.RenderTransform>
  <TranslateTransform X="{Binding Something}" Y="{Binding SomeProp, ElementName=DataContextName}"/>
</Control.RenderTransform>
Then I get the following binding error message:
[NOESIS/W] Type 'Boxed<String>' does not contain a property named 'Something'
[NOESIS/E] Binding failed: Path=Something, Source=Boxed<String>(''), Target=TranslateTransform(''), TargetProperty=TranslateTransform.x

Re: Binding to a UserControl's property which is bound to VM

Posted: 21 Aug 2021, 01:45
by yuyoyuppe
Oh, right, that makes sense, I guess it was too late that day :) It'd be still nice to have some warning/trace that the property would be overwritten though?

Re: Binding to a UserControl's property which is bound to VM

Posted: 26 Aug 2021, 13:01
by sfernandez
It'd be still nice to have some warning/trace that the property would be overwritten though?
That is what the first message tries to say when the xaml gets parsed, perhaps we can improve it by saying that previous assigned value will be ignored.