MAUI组件中派生参数Progress未更新的原因及修复方案
MAUI折扣进度卡片组件:进度条不更新的问题排查与修复
问题场景
我正在开发一个MAUI组件DiscountProgressCard,用于展示用户完成行程的进度以获取折扣。组件的XAML和后台代码如下:
组件XAML(DiscountProgressCard.xaml)
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyProject.DiscountProgressCard" x:Name="this"> <Border BindingContext="{x:Reference this}"> <!--Border styling cut for brevity --> <VerticalStackLayout> <ProgressBar ProgressColor="Green" Progress="{Binding Progress}" /> <Label> <Label.Text> <MultiBinding StringFormat="{}{0} out of {1} trips completed."> <Binding Path="CompletedTrips" /> <Binding Path="TripsNecessaryForDiscount" /> </MultiBinding> </Label.Text> </Label> </VerticalStackLayout> </Border> </ContentView>
组件后台代码(DiscountProgressCard.xaml.cs)
public partial class DiscountProgressCard : ContentView { public DiscountProgressCard() { InitializeComponent(); } // 用户已完成的行程数 public int CompletedTrips { get { return (int)GetValue(CompletedTripsProperty); } set { SetValue(CompletedTripsProperty, value); } } public static readonly BindableProperty CompletedTripsProperty = BindableProperty.Create( nameof(CompletedTrips), typeof(int), typeof(DiscountProgressCard), 0); // 获得折扣所需的行程数 public int TripsNecessaryForDiscount { get { return (int)GetValue(TripsNecessaryForDiscountProperty); } set { SetValue(TripsNecessaryForDiscountProperty, value); } } public static readonly BindableProperty TripsNecessaryForDiscountProperty = BindableProperty.Create( nameof(TripsNecessaryForDiscount), typeof(int), typeof(DiscountProgressCard), 999); // 由两个属性派生的进度值,仅组件内部使用 protected decimal Progress { get { return Decimal.Divide(CompletedTrips, TripsNecessaryForDiscount); } } }
组件使用示例(MyPage.xaml)
<ContentPage> <!--...cut for brevity...--> <components:DiscountProgressCard CompletedTrips="200" TripsNecessaryForDiscount="400" /> </ContentPage>
现象:组件渲染后,标签能正确显示200 out of 400 trips completed.,但进度条始终为空。调试发现Progress的getter仅在组件初始化时执行一次,使用的是默认值(0/999≈0),后续传入的属性值变化不会触发重新计算。
问题原因
Progress是普通CLR属性,MAUI绑定系统无法感知它的依赖属性(CompletedTrips和TripsNecessaryForDiscount)的变化。绑定系统只会在初始化时读取一次Progress的值,之后即使依赖属性更新,也不会主动重新调用Progress的getter,导致进度条无法更新。
修复方案
方案一:通过绑定属性回调更新进度
给CompletedTrips和TripsNecessaryForDiscount的绑定属性添加propertyChanged回调,当这两个属性变化时,主动通知绑定系统更新Progress的值,同时将Progress改为可绑定属性:
修改后台代码如下:
public partial class DiscountProgressCard : ContentView { public DiscountProgressCard() { InitializeComponent(); } // 用户已完成的行程数 public int CompletedTrips { get { return (int)GetValue(CompletedTripsProperty); } set { SetValue(CompletedTripsProperty, value); } } public static readonly BindableProperty CompletedTripsProperty = BindableProperty.Create( nameof(CompletedTrips), typeof(int), typeof(DiscountProgressCard), 0, propertyChanged: OnProgressDependentPropertyChanged); // 获得折扣所需的行程数 public int TripsNecessaryForDiscount { get { return (int)GetValue(TripsNecessaryForDiscountProperty); } set { SetValue(TripsNecessaryForDiscountProperty, value); } } public static readonly BindableProperty TripsNecessaryForDiscountProperty = BindableProperty.Create( nameof(TripsNecessaryForDiscount), typeof(int), typeof(DiscountProgressCard), 999, propertyChanged: OnProgressDependentPropertyChanged); // 进度值改为可绑定属性 public double Progress { get { return (double)GetValue(ProgressProperty); } private set { SetValue(ProgressProperty, value); } } public static readonly BindableProperty ProgressProperty = BindableProperty.Create( nameof(Progress), typeof(double), typeof(DiscountProgressCard), 0.0); // 依赖属性变化时更新Progress private static void OnProgressDependentPropertyChanged(BindableObject bindable, object oldValue, object newValue) { if (bindable is DiscountProgressCard card) { // 避免除以0的情况 if (card.TripsNecessaryForDiscount == 0) { card.Progress = 0; return; } card.Progress = (double)Decimal.Divide(card.CompletedTrips, card.TripsNecessaryForDiscount); } } }
方案二:在XAML中直接使用MultiBinding计算进度
无需在后台代码中定义Progress属性,直接在ProgressBar的Progress绑定上使用MultiBinding和转换器计算进度,更符合MAUI的绑定设计:
步骤1:创建进度计算转换器
public class TripProgressConverter : IMultiValueConverter { public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Length != 2 || !(values[0] is int completed) || !(values[1] is int required)) { return 0.0; } if (required == 0) return 0.0; return (double)Decimal.Divide(completed, required); } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
步骤2:修改组件XAML
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:MyProject" x:Class="MyProject.DiscountProgressCard" x:Name="this"> <ContentView.Resources> <local:TripProgressConverter x:Key="TripProgressConverter" /> </ContentView.Resources> <Border BindingContext="{x:Reference this}"> <!--Border styling cut for brevity --> <VerticalStackLayout> <ProgressBar ProgressColor="Green"> <ProgressBar.Progress> <MultiBinding Converter="{StaticResource TripProgressConverter}"> <Binding Path="CompletedTrips" /> <Binding Path="TripsNecessaryForDiscount" /> </MultiBinding> </ProgressBar.Progress> </ProgressBar> <Label> <Label.Text> <MultiBinding StringFormat="{}{0} out of {1} trips completed."> <Binding Path="CompletedTrips" /> <Binding Path="TripsNecessaryForDiscount" /> </MultiBinding> </Label.Text> </Label> </VerticalStackLayout> </Border> </ContentView>
步骤3:简化后台代码
删除原来的Progress属性,后台代码仅保留两个可绑定属性即可。
内容的提问来源于stack exchange,提问作者Muroxxas
相关产品推荐
相关产品推荐

