MAUI XAML页面属性更新正常,Blazor组件无响应该如何解决?
我有一个以单例(Singleton)形式注入到MAUI页面和Blazor组件中的MainViewModel:
public partial class MainViewModel : ObservableObject { [ObservableProperty] double? fixedIntervalStartAt; [ObservableProperty] ObservableCollection<PunchCardEntry> items = new ObservableCollection<PunchCardEntry>(); public async Task Update() { // 填充并更新上述属性和集合项 } } public partial class PunchCardEntry : ObservableObject { [ObservableProperty] DateTime dueTime; [ObservableProperty] string name; [ObservableProperty] EntryStatus status = EntryStatus.Scheduled; [ObservableProperty] bool alarmEnabled = true; }
Update()方法会被MAUI页面方法和Blazor组件调用,但仅MAUI XAML页面能正确反映数据变化:
XAML示例(正常工作)
<CollectionView ItemsSource="{Binding Items,Converter={StaticResource collectionPaginationConverter},ConverterParameter=1}"> <CollectionView.ItemTemplate> <DataTemplate x:DataType="models:PunchCardEntry"> <Border BackgroundColor="{Binding Status, Converter={StaticResource enumToColorConverter}}" Padding="12,10" Margin="0,0,0,10" StrokeThickness="0"> <Border.StrokeShape> <RoundRectangle CornerRadius="10" /> </Border.StrokeShape> <Border.Shadow> <Shadow Brush="Black" Offset="1,1" Radius="5" Opacity="0.3" /> </Border.Shadow> <Grid ColumnDefinitions="80,*,70" RowSpacing="10"> <FlexLayout Grid.Column="0" AlignItems="Center" Direction="Column" JustifyContent="Center"> <Image Source="{Binding AlarmEnabled , Converter={StaticResource boolToImageConverter}}" WidthRequest="48" HeightRequest="48"/> </FlexLayout > <StackLayout Grid.Column="1"> <Label Text="{Binding Name}" TextTransform="Uppercase" FontFamily="SubtitleFont" FontSize="14" /> <Label Text="{Binding DueTime, StringFormat='{0:t}'}" FontFamily="TitleFont" FontSize="30" FontAttributes="Bold" /> </StackLayout> <FlexLayout Grid.Column="2" AlignItems="Center" Direction="Column" JustifyContent="Center"> <Image Source="{Binding Status, Converter={StaticResource enumToImageConverter}}" WidthRequest="48" HeightRequest="48"/> </FlexLayout> </Grid> </Border> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView>
Blazor组件(无法反映更新)
@inject MainViewModel viewModel @if (Cards == null || !Cards.Any()) { <div>Loading...</div> } else { <MudStack> @foreach (var card in Cards) { <MudItem xs="12"> <PunchCard Card="@card"/> </MudItem> } </MudStack> } @code { ObservableCollection<PunchCardEntry> Cards; protected override void OnInitialized() { Cards = viewModel.Items; base.OnInitialized(); } }
PunchCard组件(上述Blazor组件引用的组件)
<MudPaper> <div> <div><MudIcon Icon="@(Card.AlarmEnabled ? Icons.Material.TwoTone.AlarmOn : Icons.Material.TwoTone.AlarmOff)" Size="Size.Large" /></div> <div> <div> <MudText >@Card.Name.ToUpper()</MudText> <MudText >@Card.DueTime.ToShortTimeString()</MudText> </div> </div> <div > <MudIcon Icon="@(Card.Status == EntryStatus.Scheduled ? Icons.Material.TwoTone.Schedule : (Card.Status == EntryStatus.Pending ? Icons.Material.TwoTone.Error : Icons.Material.TwoTone.CheckCircle))" Size="Size.Large" /> </div> </div> </MudPaper> @code { [Parameter] public PunchCardEntry Card { get; set; } }
请问我遗漏了什么配置或逻辑?
Blazor与MAUI XAML的数据绑定机制存在差异:XAML会自动处理INotifyCollectionChanged和INotifyPropertyChanged的通知,但Blazor需要手动监听这些事件来触发组件刷新,具体需要处理以下三个场景:
1. 监听集合项的增删改,刷新父Blazor组件
ObservableCollection实现了INotifyCollectionChanged,但Blazor不会自动订阅该事件。需要在父组件中手动订阅,当集合变化时调用StateHasChanged()通知组件更新:
@inject MainViewModel viewModel @implements IDisposable @if (viewModel.Items == null || !viewModel.Items.Any()) { <div>Loading...</div> } else { <MudStack> @foreach (var card in viewModel.Items) { <MudItem xs="12"> <PunchCard Card="@card"/> </MudItem> } </MudStack> } @code { protected override void OnInitialized() { base.OnInitialized(); viewModel.Items.CollectionChanged += OnItemsCollectionChanged; } private void OnItemsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { InvokeAsync(StateHasChanged); } public void Dispose() { viewModel.Items.CollectionChanged -= OnItemsCollectionChanged; } }
这里直接绑定viewModel.Items而非赋值给局部变量Cards,避免集合被整体替换时组件无法感知。
2. 监听单个卡片项的属性变化,刷新PunchCard组件
PunchCardEntry实现了INotifyPropertyChanged,但Blazor组件不会自动监听参数对象的属性变化。需要在PunchCard组件中订阅该事件,触发组件刷新:
<MudPaper> <div> <div><MudIcon Icon="@(Card.AlarmEnabled ? Icons.Material.TwoTone.AlarmOn : Icons.Material.TwoTone.AlarmOff)" Size="Size.Large" /></div> <div> <div> <MudText >@Card.Name.ToUpper()</MudText> <MudText >@Card.DueTime.ToShortTimeString()</MudText> </div> </div> <div > <MudIcon Icon="@(Card.Status == EntryStatus.Scheduled ? Icons.Material.TwoTone.Schedule : (Card.Status == EntryStatus.Pending ? Icons.Material.TwoTone.Error : Icons.Material.TwoTone.CheckCircle))" Size="Size.Large" /> </div> </div> </MudPaper> @code { [Parameter] public PunchCardEntry Card { get; set; } protected override void OnParametersSet() { base.OnParametersSet(); Card.PropertyChanged += OnCardPropertyChanged; } private void OnCardPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { InvokeAsync(StateHasChanged); } public void Dispose() { if (Card != null) { Card.PropertyChanged -= OnCardPropertyChanged; } } }
注意实现IDisposable接口取消订阅事件,避免内存泄漏。
3. 处理Items集合被整体替换的情况
如果Update方法中会直接替换Items集合(比如Items = new ObservableCollection<...>),还需要监听MainViewModel中Items属性的变化:
在父组件中补充以下代码:
protected override void OnInitialized() { base.OnInitialized(); viewModel.Items.CollectionChanged += OnItemsCollectionChanged; viewModel.PropertyChanged += ViewModel_PropertyChanged; } private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (e.PropertyName == nameof(viewModel.Items)) { viewModel.Items.CollectionChanged -= OnItemsCollectionChanged; viewModel.Items.CollectionChanged += OnItemsCollectionChanged; InvokeAsync(StateHasChanged); } } public void Dispose() { viewModel.Items.CollectionChanged -= OnItemsCollectionChanged; viewModel.PropertyChanged -= ViewModel_PropertyChanged; }
以上修改覆盖了集合增删改、单个项属性变化、集合整体替换三种场景的组件刷新需求,可解决Blazor组件不更新的问题。
内容的提问来源于stack exchange,提问作者Henrique Miranda

