.NET MAUI Image Source绑定更新后视图未刷新问题求助
问题描述
我正在开发一个采用MVVM架构和SQLite的C# .NET MAUI应用。应用包含用户ObservableCollection,点击其中项会进入对应用户的编辑视图。编辑页使用Community Toolkit的<AvatarView>展示默认png图片,点击可通过媒体选择器更换图片。
选择新图片后,模型的ImageUrl值已更新,但<AvatarView>的绑定未同步更新——新图片未显示,原图片保留,用户会误以为更换失败(实际已成功)。
新值可正确保存到SQLite数据库,点击保存按钮后,ObservableCollection也能正常更新显示新图片。我希望遵循MVVM规范,但尝试多种方法仍未解决问题。
视图代码(AddBowlerPage.xaml)
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:components="clr-namespace:TestApp.Views.Components" xmlns:vm="clr-namespace:TestApp.Views.ViewModels" x:DataType="vm:AddBowlerViewModel" x:Class="TestApp.Views.AddBowlerPage"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="70"/> </Grid.RowDefinitions> <Grid Grid.Row="0" RowDefinitions="Auto, *" ColumnDefinitions="*, Auto"> <StackLayout Spacing="5" Padding="15" Margin="10"> <components:BowlerImage ImageUrl="{Binding Bowler.ImageUrl, Mode=TwoWay}"> <components:BowlerImage.GestureRecognizers> <TapGestureRecognizer Command="{Binding SelectBowlerImageCommand}"/> </components:BowlerImage.GestureRecognizers> </components:BowlerImage> <StackLayout> <Button Text="Save Changes" HorizontalOptions="FillAndExpand" Command="{Binding SaveBowlerCommand}"> </Button> </StackLayout> </StackLayout> </Grid> </Grid> </ContentPage>
代码后台(AddBowlerPage.xaml.cs)
using TestApp.Views.ViewModels; namespace TestApp.Views; public partial class AddBowlerPage : ContentPage { public AddBowlerPage(AddBowlerViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } }
可复用图片组件(BowlerImage.xaml)
<?xml version="1.0" encoding="utf-8" ?> <ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" x:Name="this" x:Class="TestApp.Views.Components.BowlerImage"> <Grid BindingContext="{x:Reference this}"> <toolkit:AvatarView ImageSource="{Binding ImageUrl}" HorizontalOptions="Center" VerticalOptions="Center" HeightRequest="50" WidthRequest="50" Margin="10" ZIndex="1"/> </Grid> </ContentView>
视图模型(AddBowlerViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using TestApp.Models; using TestApp.Services; namespace TestApp.Views.ViewModels; [QueryProperty(nameof(Bowler), nameof(Bowler))] public partial class AddBowlerViewModel(IDatabaseService data, IShellService shell) : BaseViewModel { [ObservableProperty] private Bowler _bowler; [RelayCommand] private async Task SaveBowlerAsync() { if (Bowler is null) { return; } var (isValid, errorMessage) = Bowler.ValidateEmptyFields(); if (!isValid) { await shell.DisplayAlert("Validation Error", errorMessage, "Ok"); return; } var busyText = "Updating bowler..."; if (Bowler.Id == 0) { busyText = "Creating bowler..."; if (await data.IsBowlerExists(Bowler)) { await shell.DisplayAlert("Validation Error", "This bowler already exists", "Ok"); return; } } await ExecuteAsync(async () => { if (!(Bowler.Id == 0 ? await data.AddBowler(Bowler) : await data.UpdateBowler(Bowler))) { await shell.DisplayAlert("Update Error", "Unable to save bowler", "Ok"); } await shell.ReturnToPage(); }, busyText); } [RelayCommand] private async Task SelectBowlerImage() { var photo = await MediaPicker.Default.PickPhotoAsync(); if (photo != null) { // save the file into local storage var localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName); using Stream sourceStream = await photo.OpenReadAsync(); using FileStream localFileStream = File.OpenWrite(localFilePath); try { await sourceStream.CopyToAsync(localFileStream); Bowler.ImageUrl = localFilePath; } catch (ArgumentNullException ex) { await shell.DisplayAlert("Error", ex.Message, "Ok"); } } } }
基础视图模型(BaseViewModel.cs)
using CommunityToolkit.Mvvm.ComponentModel; namespace TestApp.Views.ViewModels; public partial class BaseViewModel : ObservableObject { [ObservableProperty] private bool _isBusy; [ObservableProperty] private string _busyText; public async Task ExecuteAsync(Func<Task> operation, string busyText = null) { IsBusy = true; BusyText = busyText ?? "Processing..."; try { await operation.Invoke(); } finally { IsBusy = false; BusyText = "Processing..."; } } }
模型类(Bowler.cs)
using SQLite; namespace TestApp.Models; [Table("bowler")] public class Bowler { [PrimaryKey, AutoIncrement] public int Id { get; set; } public string ImageUrl { get; set; } = "account_circle.png"; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string FullName => $"{FirstName} {LastName}"; }
解决方案
问题核心是**Bowler模型未实现属性变更通知**,当ImageUrl更新时无法触发UI绑定刷新。ViewModel中的Bowler是ObservableProperty,但仅在对象本身被替换时触发通知,对象内部属性变化不会传递到UI。
具体修复步骤:
- 修改
Bowler模型,继承ObservableObject并使用[ObservableProperty]特性自动生成属性变更通知:
using SQLite; using CommunityToolkit.Mvvm.ComponentModel; namespace TestApp.Models; [Table("bowler")] public partial class Bowler : ObservableObject { [PrimaryKey, AutoIncrement] public int Id { get; set; } [ObservableProperty] private string _imageUrl = "account_circle.png"; [ObservableProperty] private string _firstName = string.Empty; [ObservableProperty] private string _lastName = string.Empty; public string FullName => $"{FirstName} {LastName}"; }
说明:
[ObservableProperty]会自动生成公共属性(如ImageUrl)和对应的PropertyChanged通知,无需手动编写get/set逻辑。
- 保持现有ViewModel和XAML代码不变
修改后,当SelectBowlerImage命令中更新Bowler.ImageUrl时,会自动触发属性变更通知,UI会立即同步显示新图片。
内容的提问来源于stack exchange,提问作者buymeapc
相关产品推荐
相关产品推荐

