Faerdan
Topic Author
Posts: 61
Joined: 02 Oct 2015, 09:11
Location: Galway, Ireland
Contact:

Binding to DependencyObject Properties

17 Feb 2021, 17:03

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!

Tags:
 
User avatar
sfernandez
Site Admin
Posts: 2220
Joined: 22 Dec 2011, 19:20

Re: Binding to DependencyObject Properties

18 Feb 2021, 16:29

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}");
    }
}
 
Faerdan
Topic Author
Posts: 61
Joined: 02 Oct 2015, 09:11
Location: Galway, Ireland
Contact:

Re: Binding to DependencyObject Properties

18 Feb 2021, 19:03

This is excellent, thank you.
 
User avatar
jsantos
Site Admin
Posts: 3122
Joined: 20 Jan 2012, 17:18
Contact:

Re: Binding to DependencyObject Properties

22 Feb 2021, 14:28

Thanks! Marking as solved
 
Faerdan
Topic Author
Posts: 61
Joined: 02 Oct 2015, 09:11
Location: Galway, Ireland
Contact:

Re: Binding to DependencyObject Properties

04 Mar 2021, 17:25

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!
 
User avatar
sfernandez
Site Admin
Posts: 2220
Joined: 22 Dec 2011, 19:20

Re: Binding to DependencyObject Properties

05 Mar 2021, 14:02

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>
 
Faerdan
Topic Author
Posts: 61
Joined: 02 Oct 2015, 09:11
Location: Galway, Ireland
Contact:

Re: Binding to DependencyObject Properties

10 Mar 2021, 16:20

Thanks Sergio.

I've posted the feature request: https://www.noesisengine.com/bugs/view.php?id=1937
 
Faerdan
Topic Author
Posts: 61
Joined: 02 Oct 2015, 09:11
Location: Galway, Ireland
Contact:

Re: Binding to DependencyObject Properties

14 May 2021, 12:54

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?
 
User avatar
sfernandez
Site Admin
Posts: 2220
Joined: 22 Dec 2011, 19:20

Re: Binding to DependencyObject Properties

17 May 2021, 12:14

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).
Attachments
ParametersList.unitypackage
(4.74 KiB) Downloaded 9 times

Who is online

Users browsing this forum: No registered users and 5 guests