You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 10:07:04