How do I set RowDefinition Height to a Dynamic Resource?

337 views Asked by At

I'm trying to use DynamicResource to set the height of rows in a grid but it is not working and I am unsure why. I've created a default Xamarin project in Visual Studio to test.

Here is the relevant page:

MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TestDynamicResource.MainPage">
    <StackLayout>
    <Grid BackgroundColor="Orange">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="{DynamicResource RowHeight}" />
            <RowDefinition Height="{Binding Source={DynamicResource RowHeight}, Converter={StaticResource TestConverter}}"/>
        </Grid.RowDefinitions>
            <Label Grid.Row="0" Text="Testing Color Change on this one" TextColor="{DynamicResource LabelColor}"/>
            <Label Grid.Row="1" Text="Testing Height" />
            <Label Grid.Row="2" Text="Testing Height Change on this one through binding"/>
        </Grid>
    </StackLayout>
</ContentPage>

And here is the main App class:

  public partial class App : Application
    {
        public App()
        {
            Resources.Add("TestConverter", new TestConverter());
            Resources["RowHeight"] = new GridLength(15);
            Resources["LabelColor"] = Color.Brown;

            InitializeComponent();


            MainPage = new MainPage();

            UpdateResources();
        }

        private async void UpdateResources()
        {
            Console.WriteLine("Wait 5 seconds");
            await System.Threading.Tasks.Task.Delay(5000);

            Console.WriteLine("Change color to RED and height to 500");
            Resources["RowHeight"] = new GridLength(500);
            Resources["LabelColor"] = Color.Red;

            Console.WriteLine("Wait 5 seconds");
            await System.Threading.Tasks.Task.Delay(5000);

            Console.WriteLine("Change color to BLUE and height to 25");
            Resources["RowHeight"] = new GridLength(25);
            Resources["LabelColor"] = Color.Blue;


            Console.WriteLine("Wait 5 seconds");
            await System.Threading.Tasks.Task.Delay(5000);

            Console.WriteLine("Change color to DeepPink and height to 100");
            Resources["RowHeight"] = new GridLength(100);
            Resources["LabelColor"] = Color.DeepPink;


        }

        protected override void OnStart()
        {
        }

        protected override void OnSleep()
        {
        }

        protected override void OnResume()
        {
        }
    }

And the converter being used:

TestConverter.cs

public class TestConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is DynamicResource dr)
            {
                if (Application.Current.Resources.ContainsKey(dr.Key))
                {

                    if (Application.Current.Resources[dr.Key] is GridLength gridLengthResult)
                        return gridLengthResult;
                }
                else
                {
                    Console.WriteLine("Resources do not contain: " + dr.Key);
                }
            }

            Console.WriteLine("Returning null");
            return null;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Here is a result of when the application is started:

GIF of application

As you can see, the height never changes.

I've tried this for colors, padding, corner radius of frames, and grid row definition height -- the height is the only one that doesn't work for me.

I've also gone and implemented a converter and tried setting the row height like so:

<RowDefinition Height="{Binding Source={DynamicResource RowHeight}, Converter={StaticResource TestConverter}}"/>

But that only sets the height the first time, there are no subsequent changes.

Am I misunderstanding how binding and dynamic resources work? Or is this a bug in Xamarin?

I've figured out workarounds for this issue but would like it to work as I have it in the question.

1

There are 1 answers

4
Jessie Zhang -MSFT On

If you want to set the RowDefinition's Height to a Dynamic value. You can do as follows:

1.created a view model RowHeightClass

public class RowHeightClass: INotifyPropertyChanged 
    {
        bool swith;

        //public GridLength rowFirstHeight { set; get; }
        GridLength _rowFirstHeight;
        public GridLength RowFirstHeight
        {
            get => _rowFirstHeight;
            set => SetProperty(ref _rowFirstHeight, value);
        }


        //public GridLength rowSecondHeight { set; get; }
        GridLength _rowSecondHeight;
        public GridLength RowSecondHeight
        {
            get => _rowSecondHeight;
            set => SetProperty(ref _rowSecondHeight, value);
        }

        public RowHeightClass()
        {
            //initial RowFirstHeight and  RowSecondHeight
            RowFirstHeight = new GridLength(15);

            RowSecondHeight = new GridLength(15);
        }


        public ICommand ResetHeightCommand => new Command(resetHeight);

        private void resetHeight()
        { //reset RowFirstHeight and  RowSecondHeight
            if (!swith)
            {
                RowFirstHeight = new GridLength(50);
                RowSecondHeight = new GridLength(50);
            }
            else {
                RowFirstHeight = new GridLength(15);
                RowSecondHeight = new GridLength(15);
            }

            swith = !swith;
        }


        bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value))
                return false;

            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

2.bind the properties in MainPage.xaml as follows:

<?xml version="1.0" encoding="utf-8" ?> 
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DynamicHeightApp.MainPage">

    <StackLayout>
        <Grid BackgroundColor="Orange">
            <Grid.RowDefinitions>
                <RowDefinition Height="{Binding RowFirstHeight}" />
                <RowDefinition Height="{Binding RowSecondHeight}"/>
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <Label Grid.Row="0" Text="Testing Color Change on this one" TextColor="Red"/>
            <Label Grid.Row="1" Text="Testing Height" />
            <Label Grid.Row="2" Text="Testing Height Change on this one through binding"/>
        </Grid>

        <Button  Text="reset" Command="{Binding ResetHeightCommand}"/>
    </StackLayout>

</ContentPage>

3.MainPage.xaml.cs

public partial class MainPage : ContentPage 
{
    RowHeightClass rowHeightClass;

    public MainPage()
    {
        InitializeComponent();

         rowHeightClass = new RowHeightClass();

        BindingContext = rowHeightClass;
    }
}

Note:

I added two properties RowFirstHeight and RowSecondHeight and implemented interface INotifyPropertyChanged for this view model, if we change the value of above properties, the UI will update automatically.