Page 1 of 1

Binding to DependencyObject Properties

Posted: 17 Feb 2021, 17:03
by Faerdan
I want to pass some objects (of a particular type) to a DependencyProperty in XAML, and have the values of properties on those objects set via bindings.

I've tried a number of methods, but none have worked.

This was my latest attempt:
C#
public class ParameterDependencyObject : DependencyObject
{

    public static readonly DependencyProperty KeyProperty =
        DependencyProperty.Register(nameof(Key), typeof(string), typeof(ParameterDependencyObject), new PropertyMetadata(null));

    public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register(nameof(Value), typeof(string), typeof(ParameterDependencyObject), new PropertyMetadata(null));

    public string Key
    {
        get { return (string)GetValue(KeyProperty); }
        set { SetValue(KeyProperty, value); }
    }

    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
}

[ContentProperty("Parameters")]
public class TextParametersDependencyObject : DependencyObject
{
    public ObservableCollection<ParameterDependencyObject> Parameters
    {
        get { return (ObservableCollection<ParameterDependencyObject>)GetValue(ParametersProperty); }
        set { SetValue(ParametersProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Parameters.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ParametersProperty =
        DependencyProperty.Register("Parameters", typeof(ObservableCollection<ParameterDependencyObject>), typeof(TextParametersDependencyObject), new PropertyMetadata(null));

}
XAML:
<common:TextContentControl HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource common_style_hermes_textShadowedNormal1}" Text="((common_txt_tripleInsertionTEST))" >
    <!-- TextContentControl has a TextParametersDependencyObject TextFormatParametersProperty -->
    <common:TextContentControl.TextFormatParameters>
        <viewModels:TextParametersDependencyObject>
            <!-- Works fine -->
            <viewModels:ParameterDependencyObject Key="Key1"  Value="This works"/>
            <!-- Error in Unity "StaticResource 'airshipHUD_string_test' not found." -->
            <viewModels:ParameterDependencyObject Key="Key2" Value="{StaticResource airshipHUD_string_test}" />
            <!-- Error in Unity "StaticResource 'airshipHUD_string_test' not found." -->
            <viewModels:ParameterDependencyObject Key="Key3" Value="{Binding CurrentSpeed}" />
        </viewModels:TextParametersDependencyObject>
    </common:TextContentControl.TextFormatParameters>
</common:TextContentControl>
Could you explain why this doesn't work? And perhaps suggest another method to achieve a similar result?

Thanks!

Re: Binding to DependencyObject Properties

Posted: 18 Feb 2021, 16:29
by sfernandez
It is not working because the items in your collection are not part of the logical tree so resources and bindings can't walk up the tree to resolve the references.

You can use a FreezableCollection that automatically connects its items to the tree:
public class ParameterCollection : FreezableCollection<Parameter> { }

[ContentProperty("Parameters")]
public class ParameterList : DependencyObject
{
    public ParameterList()
    {
        Parameters = new ParameterCollection();
    }

    public ParameterCollection Parameters
    {
        get { return (ParameterCollection)GetValue(ParametersProperty); }
        set { SetValue(ParametersProperty, value); }
    }

    public static readonly DependencyProperty ParametersProperty = DependencyProperty.Register(
        "Parameters", typeof(ParameterCollection), typeof(ParameterList),
        new PropertyMetadata(null));
}

public class Parameter : DependencyObject
{
    public string Key
    {
        get { return (string)GetValue(KeyProperty); }
        set { SetValue(KeyProperty, value); }
    }

    public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
        "Key", typeof(string), typeof(Parameter),
        new PropertyMetadata(string.Empty));


    public string Value
    {
        get { return (string)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        "Value", typeof(string), typeof(Parameter),
        new PropertyMetadata(string.Empty, OnValueChanged));

    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Parameter param = (Parameter)d;
        UnityEngine.Debug.Log($"Key={param.Key}, Value={e.NewValue}");
    }
}

Re: Binding to DependencyObject Properties

Posted: 18 Feb 2021, 19:03
by Faerdan
This is excellent, thank you.

Re: Binding to DependencyObject Properties

Posted: 22 Feb 2021, 14:28
by jsantos
Thanks! Marking as solved

Re: Binding to DependencyObject Properties

Posted: 04 Mar 2021, 17:25
by Faerdan
Hi Jesús and Sergio,

I have just got arround to testing this.. but it only works in dependency properties which add their values to the logical tree, such as ContentControl.Content.

Such as this, where ParameterViewData inherits from FrameworkElement:
                <common:TextContentControl Text="((holdingsScreen_txt_title))">
                    <common:TextContentControl.Content>
                        <common:ParameterViewData Key="key2" Value="{Binding CurrenciesData.Fuel}" />
                    </common:TextContentControl.Content>
                </common:TextContentControl>
Here ParameterViewData becomes part of the logical tree and so {Binding CurrenciesData.Fuel} binds correctly.

However if I define a new dependency property, the value is not added to the logical tree and so the binding fails:
                <common:TextContentControl Text="((holdingsScreen_txt_title))">
                    <common:TextContentControl.TextParameters>
                        <common:ParameterViewData Key="key2" Value="{Binding CurrenciesData.Fuel}" />
                    </common:TextContentControl.TextParameters>
                </common:TextContentControl>
In WPF I could use FrameworkElement.AddLogicalChild(object) to add the ParameterViewData value to the logical tree, which allows the {Binding CurrenciesData.Fuel} binding to work. However Noesis does not implement FrameworkElement.AddLogicalChild(object)/FrameworkElement.RemoveLogicalChild(object).
        private static void OnTextParametersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextContentControl textContentControl)
            {
                if (e.OldValue != null)
                {
                    textContentControl.RemoveLogicalChild(e.OldValue);
                }
                if (e.NewValue != null)
                {
                    textContentControl.AddLogicalChild(e.NewValue);
                }
            }

        }
        
Will you ever be implementing these methods, or is there another solution to allow this behaviour?

Thanks!

Re: Binding to DependencyObject Properties

Posted: 05 Mar 2021, 14:02
by sfernandez
FrameworkElement AddLogicalChild() and RemoveLogicalChild() is something we should add in a future release, could you please report it in our bugtracker?

In the meantime you can use a Binding with ElementName like this:
<common:TextContentControl x:Name="textControl" Text="((holdingsScreen_txt_title))">
  <common:TextContentControl.TextParameters>
    <common:ParameterViewData Key="key2" Value="{Binding DataContext.CurrenciesData.Fuel, ElementName=textControl}" />
  </common:TextContentControl.TextParameters>
</common:TextContentControl>

Re: Binding to DependencyObject Properties

Posted: 10 Mar 2021, 16:20
by Faerdan
Thanks Sergio.

I've posted the feature request: https://www.noesisengine.com/bugs/view.php?id=1937

Re: Binding to DependencyObject Properties

Posted: 14 May 2021, 12:54
by Faerdan
Hi guys,

The workaround you provided doesn't work:
Image

I've tired this same pattern in a number of different cases, none of them bind successfully (though the binding is correct).
Static values do work.

Any update on when https://www.noesisengine.com/bugs/view.php?id=1937 will be in?

Re: Binding to DependencyObject Properties

Posted: 17 May 2021, 12:14
by sfernandez
Hi, as the parameter objects are part of a template, to correctly clone them when applying the template they should inherit from Freezable.
I created and attached a small sample that demonstrates this working.

Regarding the bug #1937 I've been doing some tests and the implementation turned to be more complex than expected so we are leaving it for the upcoming major release (due in July before GDC).