如何为Grid的RowDefinition Height设置DynamicResource?
1. DynamicResource在RowDefinition.Height上的限制
Xamarin.Forms中,RowDefinition.Height虽为可绑定属性,但Grid的行定义集合(RowDefinitions)初始化后,不会自动监听单个RowDefinition的Height属性变更通知。像Label.TextColor这类视觉元素属性,继承自VisualElement,DynamicResource变更时会触发属性变更进而更新UI;但RowDefinition不属于视觉元素范畴,其属性变更无法触发Grid的重新布局。
2. 转换器方案的缺陷
你的转换器仅在绑定初始化时执行一次,直接读取当时的RowHeight资源值,但没有监听资源的后续变更。因为绑定的Source是DynamicResource对象本身,该对象不会随资源值变化而改变,所以转换器不会重新执行,导致后续行高更新无法生效。
方案1:手动触发Grid布局更新
更新RowHeight资源后,手动调用Grid的ForceLayout()方法,强制Grid重新计算布局。
修改App.xaml.cs中的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; // 获取MainPage中的Grid并强制布局 if (MainPage is MainPage mainPage) { var grid = mainPage.FindByName<Grid>("testGrid"); grid?.ForceLayout(); } 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; if (MainPage is MainPage mainPage2) { var grid2 = mainPage2.FindByName<Grid>("testGrid"); grid2?.ForceLayout(); } 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; if (MainPage is MainPage mainPage3) { var grid3 = mainPage3.FindByName<Grid>("testGrid"); grid3?.ForceLayout(); } }
同时给MainPage中的Grid命名:
<Grid x:Name="testGrid" BackgroundColor="Orange">
方案2:使用绑定结合ViewModel(推荐)
放弃DynamicResource,改用MVVM绑定方式,将行高绑定到ViewModel的属性,通过INotifyPropertyChanged触发更新,Grid会响应属性变更:
- 创建ViewModel:
using System.ComponentModel; using System.Runtime.CompilerServices; using Xamarin.Forms; namespace TestDynamicResource { public class MainViewModel : INotifyPropertyChanged { private GridLength _rowHeight; public GridLength RowHeight { get => _rowHeight; set { _rowHeight = value; OnPropertyChanged(); } } private Color _labelColor; public Color LabelColor { get => _labelColor; set { _labelColor = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
- 修改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" xmlns:local="clr-namespace:TestDynamicResource" x:Class="TestDynamicResource.MainPage"> <ContentPage.BindingContext> <local:MainViewModel /> </ContentPage.BindingContext> <StackLayout> <Grid BackgroundColor="Orange"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="{Binding RowHeight}" /> <RowDefinition Height="{Binding RowHeight}"/> </Grid.RowDefinitions> <Label Grid.Row="0" Text="Testing Color Change on this one" TextColor="{Binding LabelColor}"/> <Label Grid.Row="1" Text="Testing Height" /> <Label Grid.Row="2" Text="Testing Height Change on this one through binding"/> </Grid> </StackLayout> </ContentPage>
- 修改App.xaml.cs中的更新逻辑:
private async void UpdateResources() { var viewModel = (MainViewModel)((MainPage)MainPage).BindingContext; Console.WriteLine("Wait 5 seconds"); await System.Threading.Tasks.Task.Delay(5000); Console.WriteLine("Change color to RED and height to 500"); viewModel.RowHeight = new GridLength(500); viewModel.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"); viewModel.RowHeight = new GridLength(25); viewModel.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"); viewModel.RowHeight = new GridLength(100); viewModel.LabelColor = Color.DeepPink; }
方案3:自定义可监听资源变更的RowDefinition
创建自定义RowDefinition类,监听Application资源的变更,自动更新Height属性:
using Xamarin.Forms; namespace TestDynamicResource { public class ObservableRowDefinition : RowDefinition { private string _resourceKey; public string ResourceKey { get => _resourceKey; set { if (_resourceKey != value) { if (!string.IsNullOrEmpty(_resourceKey)) { Application.Current.Resources.RemoveResourceChangedListener(OnResourceChanged); } _resourceKey = value; if (!string.IsNullOrEmpty(_resourceKey)) { Application.Current.Resources.AddResourceChangedListener(OnResourceChanged); UpdateHeightFromResource(); } } } } private void OnResourceChanged(object sender, ResourcesChangedEventArgs e) { if (e.Key == _resourceKey) { UpdateHeightFromResource(); } } private void UpdateHeightFromResource() { if (Application.Current.Resources.TryGetValue(_resourceKey, out var value) && value is GridLength length) { Height = length; } } } }
然后在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" xmlns:local="clr-namespace:TestDynamicResource" x:Class="TestDynamicResource.MainPage"> <StackLayout> <Grid BackgroundColor="Orange"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <local:ObservableRowDefinition ResourceKey="RowHeight" /> <local:ObservableRowDefinition ResourceKey="RowHeight" /> </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>
这种方式可以保留DynamicResource的资源管理方式,同时自动响应资源变更。
不是你的理解错误,而是Xamarin.Forms中RowDefinition的属性变更通知机制和视觉元素不同,导致DynamicResource的变更无法自动触发Grid布局更新。上述方案中,MVVM绑定的方式最符合Xamarin.Forms的设计规范,自定义ObservableRowDefinition则能保留资源管理的灵活性。
内容的提问来源于stack exchange,提问作者Ibrennan208

