BindableProperty does not notify when its property is set

RedRaRedRa Member ✭✭✭

Hi all,

I am developing my own DynamicGrid and I have added to it BindableProperty:

        public static readonly BindableProperty DynamicRowDefinitionProperty =
            BindableProperty.Create( // Or BindableProperty.CreateAttached it does not matter
                "DynamicRowDefinition",
                typeof(RowDefinition),
                typeof(DynamicGrid),
                defaultValue: null,
                defaultValueCreator: bindable =>
                {
                    var rowDef = new RowDefinition();
                    rowDef.SizeChanged += ((DynamicGrid)bindable).OnDefinitionChanged;
                    return rowDef;
                },
                propertyChanged: OnDynamicRowDefinitionPropertyChanged);

        public static void OnDynamicRowDefinitionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if (oldValue != newValue)
            {
            }
        }

        public void OnDefinitionChanged(object bindable, EventArgs newValue)
        {
            Console.WriteLine("OnDefinitionChanged");
        }

        public RowDefinition DynamicRowDefinition
        {
            get { return (RowDefinition)GetValue(DynamicRowDefinitionProperty); }
            protected set { SetValue(DynamicRowDefinitionProperty, value); }
        }

, but when I try to set Height of DynamicRowDefinition it does not set Height :

<utilscontrols:DynamicGrid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
    <utilscontrols:DynamicGrid.DynamicRowDefinition
                Height="20" />
     <!-- ... -->
</utilscontrols:DynamicGrid>

But by some reason I do not receive OnDefinitionChanged when Height is set to value "20"

What did I wrong ?

Best Answer

Answers

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 28

    Maybe because DynamicRowDefinition has a protected setter, try to change it to public (or internal if you insist on not using public for any reason)? protected setter means only the class and its children can set the value, the XAML page can't do it.

  • RedRaRedRa Member ✭✭✭
    edited June 28

    @Amar_Bait said:
    Maybe because DynamicRowDefinition has a protected setter, try to change it to public (or internal if you insist on not using public for any reason)? protected setter means only the class and its children can set the value, the XAML page can't do it.

    No, I tried it, but it's not helped !!
    I am changing not the original bindable property, but its own property Height ...

  • LucasZhangLucasZhang Member, Xamarin Team Xamurai
    edited June 28

    Firstly , It seems that you should bind a property to RowDefinitionCollection not RowDefinition .

    In addition , Why you create such a binding property ? The height of RowDefinition is a binding property , So you can use data binding and receive the change of it in your ViewModel .

  • JoeMankeJoeManke USMember ✭✭✭✭✭

    Because you are only registering for the SizeChanged event in the default value creator. That block of code is used to create the default value for when you do not set the property yourself. When you create a DynamicRowDefinition object in your XAML you are throwing away that default value. If you register for the event in your XAML I can't guarantee it will fire for your initial setting of the height, but it should if you change it later.

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 28

    OnDynamicRowDefinitionPropertyChanged should trigger, but the method is empty, try inserting the same code in it as defaultValueCreator. Also I insist you should make your property setter public.

    public static void OnDynamicRowDefinitionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var grid = bindable as DynamicGrid;
        var rowDef = new RowDefinition();
        rowDef.SizeChanged += grid.OnDefinitionChanged;
        grid.ColumnDefinitions.Add(rowDef); // Don't know how your control is supposed to work just guessing here...
    }
    
  • RedRaRedRa Member ✭✭✭

    @LucasZhang said:
    Firstly , It seems that you should bind a property to RowDefinitionCollection not RowDefinition .

    In addition , Why you create such a binding property ? The height of RowDefinition is a binding property , So you can use data binding and receive the change of it in your ViewModel .

    Because this is an idea, I set in XAML RowDefinition which will be applied to each next row

  • RedRaRedRa Member ✭✭✭

    @Amar_Bait said:
    OnDynamicRowDefinitionPropertyChanged should trigger, but the method is empty, try inserting the same code in it as defaultValueCreator. Also I insist you should make your property setter public.

    public static void OnDynamicRowDefinitionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
      var grid = bindable as DynamicGrid;
      var rowDef = new RowDefinition();
      rowDef.SizeChanged += grid.OnDefinitionChanged;
      grid.ColumnDefinitions.Add(rowDef); // Don't know how your control is supposed to work just guessing here...
    }
    

    Guys you do not understand, I do not need all change the property, it is possible to do like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition="{<Custom MarkUpExtension>}"/>
    

    I just want to set for Property of DynamicRowDefinition of DynamicGrid ...
    Is it possible to do ?

    I want to do it something like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition
                    Height="50" />
    
  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 28

    It's not us who don't understand, it's you who poorly explain your issue.

    You're already setting it and its height property to "50".

    You can't have content inside your DynamicRowDefinition property, as it is of type RowDefinition, which doesn't accept content.

    You can create a custom DynamicRowDefinition class that exposes a Height and a Content property of type View if you want your dynamic rows to have each one a unique content.

    Or if all DynamicRowDefinition use the same content template, then add a DynamicGridContent property of type View to the DynamicGrid itself, then you can do:

    <utilscontrols:DynamicGrid.DynamicGridContent>
        <Label Text="I'm content!">
    </utilscontrols:DynamicGrid.DynamicGridContent>
    
  • RedRaRedRa Member ✭✭✭

    @Amar_Bait said:
    It's not us who don't understand, it's you who poorly explain your issue.

    You're already setting it and its height property to "50".

    It is not ... An issue is that when I take this Height property its value is not 50 ?!
    How it could be possible ?
    But in XAML it is set ... I understand nothing ...

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 28

    As @JoeManke and I told you in this comment, when a property changes, Xamarin.Forms calls OnDynamicRowDefinitionPropertyChanged, which is empty in your code. Put a breakpoint in that method and you will see that it will get called. Put any logic you need there.

    Also you are not really clear in explaining your problem. Sometimes you say you need to put XAML inside, sometimes that the Height property is not set... At least try what is suggested here (by developers who have lot more experience than you) and tell us what you got. Also how do you know the Height property is not 50? (show code how you retrieve the value)

    EDIT: Just understood what you want (I hope...)

    public static void OnDynamicRowDefinitionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var grid = bindable as DynamicGrid;
    
        var oldDynamicRowDef = oldValue as RowDefinition;
        var dynamicRowDef = newValue as RowDefinition;
    
        if (oldDynamicRowDef != null)
            oldDynamicRowDef.SizeChanged -= grid.OnDefinitionChanged;
    
        if (dynamicRowDef != null)
        {
            dynamicRowDef.SizeChanged += grid.OnDefinitionChanged;
            Console.WriteLine(dynamicRowDef.Height) // Should print 50...   
        }
    }
    
  • RedRaRedRa Member ✭✭✭

    @Amar_Bait said:
    As @JoeManke and I told you in this comment, when a property changes, Xamarin.Forms calls OnDynamicRowDefinitionPropertyChanged, which is empty in your code. Put a breakpoint in that method and you will see that it will get called. Put any logic you need there.

    Also you are not really clear in explaining your problem. Sometimes you say you need to put XAML inside, sometimes that the Height property is not set... At least try what is suggested here (by developers who have lot more experience than you) and tell us what you got. Also how do you know the Height property is not 50? (show code how you retrieve the value)

    EDIT: Just understood what you want (I hope...)

    public static void OnDynamicRowDefinitionPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
      var grid = bindable as DynamicGrid;
    
      var oldDynamicRowDef = oldValue as RowDefinition;
      var dynamicRowDef = newValue as RowDefinition;
    
      if (oldDynamicRowDef != null)
          oldDynamicRowDef.SizeChanged -= grid.OnDefinitionChanged;
    
      if (dynamicRowDef != null)
      {
          dynamicRowDef.SizeChanged += grid.OnDefinitionChanged;
          Console.WriteLine(dynamicRowDef.Height) // Should print 50...   
      }
    }
    

    I have updated code with the following:

            public DynamicGrid()
                : base()
            {
                Children = new DynamicGridList(this);
                DynamicRowDefinition.PropertyChanged += (object sender, PropertyChangedEventArgs e) =>
                {
                    Console.WriteLine("Hi from OnDefinitionChanged");
                };
                // If I remove line below then I will not receive update of Height even that I set this property in XAML
                DynamicRowDefinition.Height = 50.0;
            }
    
            public static readonly BindableProperty DynamicRowDefinitionProperty =
                BindableProperty.Create(
                    "DynamicRowDefinition",
                    typeof(RowDefinition),
                    typeof(DynamicGrid),
                    defaultValue: new RowDefinition(),
                    defaultValueCreator: bindable =>
                    {
                        var grid = bindable as DynamicGrid;
                        var rowDef = new RowDefinition();
                        rowDef.SizeChanged += grid.OnDefinitionChanged;
                        return rowDef;
                    },
                    propertyChanged: (bindable, oldValue, newValue) =>
                    {
                        var grid = bindable as DynamicGrid;
    
                        var oldDynamicRowDef = oldValue as RowDefinition;
                        var dynamicRowDef = newValue as RowDefinition;
    
                        if (oldDynamicRowDef != null)
                        {
                            oldDynamicRowDef.SizeChanged -= grid.OnDefinitionChanged;
                        }
    
                        if (dynamicRowDef != null)
                        {
                            dynamicRowDef.SizeChanged += grid.OnDefinitionChanged;
                            Console.WriteLine(dynamicRowDef.Height); // Should print 50...   
                        }
                    });
    
            void OnDefinitionChanged(object sender, EventArgs e)
            {
                Console.WriteLine("Hi from OnDefinitionChanged");
            }
    
            public RowDefinition DynamicRowDefinition
            {
                get { return (RowDefinition)GetValue(DynamicRowDefinitionProperty); }
                set { SetValue(DynamicRowDefinitionProperty, value); }
            }
    

    If I remove line of updating Height in constructor then I will not receive update of Height even that I set this property in XAML:

    <utilscontrols:DynamicGrid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
    
                <utilscontrols:DynamicGrid.DynamicRowDefinition
                    Height="50" />
    
                <!-- Other code -->
    
    </utilscontrols:DynamicGrid>
    

    I just want to update from XAML property (Height) of BindableProperty (DynamicRowDefinitionProperty), but when I set Height in XAML I do not receive any notifications at all

  • RedRaRedRa Member ✭✭✭
    edited June 30

    @Amar_Bait said:
    Because your XAML is wrong, it should be like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition>
      <RowDefinition Height="50" />
    </utilscontrols:DynamicGrid.DynamicRowDefinition>
    

    Yes, right now it works ...
    But why it did not work previously and is there a way make it works like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition Height="50" />
    
  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 30
    Because how that's XAML (and XML in general) works. Since your property is of a complex type, you should write XAML like this. Now if you're only interested in the Height property, then why don't you make the property of type double instead of RowDefinition? Like that it will be even simpler in XAML:

    < utilscontrols:DynamicGrid DynamicRowHeight="50" />
  • RedRaRedRa Member ✭✭✭
    edited June 30

    @Amar_Bait said:
    Because how that's XAML (and XML in general) works. Since your property is of a complex type, you should write XAML like this. Now if you're only interested in the Height property, then why don't you make the property of type double instead of RowDefinition? Like that it will be even simpler in XAML:


    &lt; utilscontrols:DynamicGrid DynamicRowHeight="50" /&gt;

    Thank you, Amar_Bait, for support !!

    According the your explanation I got that when I am doing something like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition Height="50" />
    

    I specify that I create object of type DynamicRowDefinition and set Height="50" to it
    Am I right ?
    And if yes, why in such case it is not an error during compilation ?

    The following code does not work for me, because it creates a new instance of DynamicGrid:

    <utilscontrols:DynamicGrid DynamicRowHeight="50" />
    
  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 30

    @RedRa said:
    According the your explanation I got that when I am doing something like this:

    <utilscontrols:DynamicGrid.DynamicRowDefinition Height="50" />
    

    I specify that I create object of type DynamicRowDefinition and set Height="50" to it
    Am I right ?

    No.

    utilscontrols:DynamicGrid.DynamicRowDefinition in XAML is a reference to the property itself, not its value. Same for any complex property element. You can't do:

    <Label>
        <Label.Text OtherPropertyHere="It gets ignored by Xamarin Forms">
            Text here...
        </Label.Text>
    </Label>
    

    I suggets you to read again about XAML in Xamarin Docs. The documentation specifically says:

    In XAML, however, this syntax is very special. One of the rules for property elements is that nothing else can appear in the Label.TextColor tag. The value of the property is always defined as content between the property-element start and
    end tags.

    I agree that the XAML compiler should throw an exception saying clearly that nothing should appear in a complex property element tag.

    The following code does not work for me, because it creates a new instance of DynamicGrid:

    <utilscontrols:DynamicGrid DynamicRowHeight="50" />
    

    What I meant by DynamicRowHeight it to create new property called DynamicRowHeight of type GridLength, instead of DynamicRowDefinition of type RowDefinition, if you only need to have a Height property...

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 30

    If you explain us what you're trying to do, I think we could probably find a better and less complicated way to do it than your solution. If you're trying to build a Grid with dynamic Row heights than you can use normal RowDefinition with its Height property set to "Auto", it will resize itself automatically to its child size. Also RowDefinition.Height property is a bindable property itself, so you can bind it to any property you want, then you just update the property and the Height will be updated accordingly.

  • RedRaRedRa Member ✭✭✭

    @Amar_Bait said:
    If you explain us what you're trying to do, I think we could probably find a better and less complicated way to do it than your solution. If you're trying to build a Grid with dynamic Row heights than you can use normal RowDefinition with its Height property set to "Auto", it will resize itself automatically to its child size. Also RowDefinition.Height property is a bindable property itself, so you can bind it to any property you want, then you just update the property and the Height will be updated accordingly.

    Okay, thank you @Amar_Bait !!

    I understood that I used XAML wrongly acording the article https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/essential-xaml-syntax

    I am building right now DynamicGrid Control which can contain dynamic number of items, that is why I needed RowHeight, because I want to set Height for all rows ...

    Anyway thank you a lot, and all other guys who supported me !!

  • RedRaRedRa Member ✭✭✭

    I have finished implementation of custom grid ...
    Look at result in XAML:

                <utilscontrols:DynamicGrid
                    x:Name="Grid"
                    RowHeight="50"
                    ItemsSource="{Binding Sources}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
    
                    <Label
                        Grid.Row="1"
                        Grid.Column="1"
                        Grid.ColumnSpan="2"
                        Text="Label at position (1, 1[2])"
                        VerticalOptions="CenterAndExpand" 
                        HorizontalOptions="CenterAndExpand" />
                    <Label
                        Grid.Row="2"
                        Grid.RowSpan="2"
                        Grid.Column="0"
                        Text="Label at position (2[2], 2)"
                        VerticalOptions="CenterAndExpand" 
                        HorizontalOptions="CenterAndExpand" />
                    <Label
                        Grid.Row="3"
                        Grid.Column="2"
                        Grid.ColumnSpan="2"
                        Text="Label at position (3, 2[2])"
                        VerticalOptions="CenterAndExpand" 
                        HorizontalOptions="CenterAndExpand" />
                </utilscontrols:DynamicGrid>
    
Sign In or Register to comment.