MVVM模式下Picker绑定SelectedItem时的选中项传递问题
MVVM模式下Picker控件更新数据匹配问题
我采用MVVM模式开发,新增与展示功能运行正常,但实现更新功能时碰到Picker控件的问题:SelectedItem已绑定新数据,不清楚如何将展示页面的Picker选中项传递到编辑页面。已通过QueryProperty将选中的Dungeon对象传递至UpdateDungeonPage,但Picker无法正确匹配原有数据;试过SelectedIndex方法,仅支持int类型,并非最优方案。
相关代码实现
DisplayViewModel中的UpdateDungeon方法
public async void UpdateDungeon() { Dictionary<string, object> newParm = new Dictionary<string, object>(); newParm.Add("DungeonDetails", selectedDungeon); await AppShell.Current.GoToAsync(nameof(UpdateDungeonPage), newParm); }
展示页面ListView(dungeondisplay.xaml)
<ListView ItemsSource="{Binding dungeonsCollection}" IsRefreshing="True" ItemSelected="OnItemSelected"> <ListView.Header> <HorizontalStackLayout Spacing="10"> <Label Text="Name" /> <Label Text="Level" /> <Label Text="Enchant" /> <Label Text="Zone" /> <Label Text="Type" /> </HorizontalStackLayout> </ListView.Header> <ListView.ItemTemplate> <DataTemplate x:DataType="models:Dungeon"> <ViewCell> <StackLayout Spacing="15"> <HorizontalStackLayout Spacing="10"> <Label Text="{Binding name}" /> <Label Text="{Binding level}" /> <Label Text="{Binding enchant}" /> <Label Text="{Binding zone}" /> <Label Text="{Binding type}" /> </HorizontalStackLayout> </StackLayout> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView>
ItemSelected事件(dungeondisplaypage.xaml.cs)
public void OnItemSelected(object sender, SelectedItemChangedEventArgs args) { if (args.SelectedItem == null) { return; } _viewModel.selectedDungeon = args.SelectedItem as Dungeon; }
编辑页面Picker(UpdateDungeonPage.xaml)
<Picker ItemsSource="{Binding TierList}" ItemDisplayBinding="{Binding Tier}" SelectedItem="{Binding SelectedTier}" />
UpdateDungeonViewModel.cs
[QueryProperty(nameof(DungeonDetails), "DungeonDetails")] public partial class UpdateDungeonPageViewModel : ObservableObject { [ObservableProperty] private Dungeon _dungeonDetails = new Dungeon(); private readonly IDungeonService _service; public List<DungeonTier> TierList { get; set; } public List<DungeonEnchant> EnchantList { get; set; } public UpdateDungeonPageViewModel(IDungeonService dungeonService) { _service = dungeonService; TierList = new List<DungeonTier>(); TierList.Add(new DungeonTier() { Id = 0, Tier = "4" }); TierList.Add(new DungeonTier() { Id = 1, Tier = "5" }); TierList.Add(new DungeonTier() { Id = 2, Tier = "6" }); TierList.Add(new DungeonTier() { Id = 3, Tier = "7" }); TierList.Add(new DungeonTier() { Id = 4, Tier = "8" }); EnchantList = new List<DungeonEnchant>(); EnchantList.Add(new DungeonEnchant() { Id = 0, Enchant = 0 }); EnchantList.Add(new DungeonEnchant() { Id = 1, Enchant = 1 }); EnchantList.Add(new DungeonEnchant() { Id = 2, Enchant = 2 }); EnchantList.Add(new DungeonEnchant() { Id = 3, Enchant = 3 }); } public event PropertyChangingEventHandler PropertyChanged; void OnPropertyChanged([CallerMemberName] string propertyName = "") { if (PropertyChanged == null) { return; } PropertyChanged.Invoke(this, new PropertyChangingEventArgs(propertyName)); } [ObservableProperty] private DungeonTier _level; public DungeonTier SelectedTier { get { return Level; } set { Level = value; OnPropertyChanged(); } } [ObservableProperty] private DungeonEnchant _enchant; public DungeonEnchant SelectedEnchant { get { return Enchant; } set { Enchant = value; OnPropertyChanged(); } } [RelayCommand] public async void UpdateDungeon() { DungeonDetails.enchant = Enchant.Enchant; DungeonDetails.level = Level.Tier; int respone = -1; if (DungeonDetails.id > 0) { respone = await _service.UpdateItemAsync(DungeonDetails); } await Shell.Current.Navigation.PopAsync(); } }
DungeonDisplay.xaml.cs完整代码
protected DungeonDisplayViewModel _viewModel; public DungeonDisplay(DungeonDisplayViewModel viewModel) { InitializeComponent(); _viewModel = viewModel; this.BindingContext = viewModel; } public void OnItemSelected(object sender, SelectedItemChangedEventArgs args) { if (args.SelectedItem == null) { return; } _viewModel.selectedDungeon = args.SelectedItem as Dungeon; } protected override void OnAppearing() { base.OnAppearing(); _viewModel.GetDungeonList(); }
DungeonDisplayViewModel.cs完整代码
public ObservableCollection<Dungeon> dungeonsCollection { get; set; } = new ObservableCollection<Dungeon>(); private readonly IDungeonService _service; public Dungeon selectedDungeon { get; set; } = new Dungeon(); public DungeonDisplayViewModel(IDungeonService dungeonService) { _service = dungeonService; } [RelayCommand] public async void GetDungeonList() { var dungeonList = await _service.GetAllAsync(); if (dungeonList?.Count > 0 ) { dungeonsCollection.Clear(); foreach( var dungeon in dungeonList ) { dungeonsCollection.Add(dungeon); } } } [RelayCommand] public async void AddDungeon() { await AppShell.Current.GoToAsync(nameof(AddDungeonPage)); } [RelayCommand] public async void UpdateDungeon() { Dictionary<string, object> newParm = new Dictionary<string, object>(); newParm.Add("DungeonDetails", selectedDungeon); await AppShell.Current.GoToAsync(nameof(UpdateDungeonPage),newParm); }
解决方案
Picker无法匹配原有数据的核心原因是:SelectedItem的匹配依赖对象引用相等,传递过来的DungeonDetails中的level(字符串)和TierList中的DungeonTier对象不是同一个引用,所以Picker找不到对应的选中项。
步骤1:修正ObservableObject的PropertyChanged实现
ViewModel继承了ObservableObject,但自定义的PropertyChanged事件和OnPropertyChanged方法会导致CommunityToolkit的自动属性通知失效,直接删除以下代码:
public event PropertyChangingEventHandler PropertyChanged; void OnPropertyChanged([CallerMemberName] string propertyName = "") { if (PropertyChanged == null) { return; } PropertyChanged.Invoke(this, new PropertyChangingEventArgs(propertyName)); }
步骤2:在DungeonDetails赋值后匹配Picker选项
利用CommunityToolkit的[ObservableProperty]回调,在DungeonDetails被赋值后,从列表中找到对应的数据对象,赋值给选中项属性:
[ObservableProperty] private Dungeon _dungeonDetails = new Dungeon(); // 当DungeonDetails属性变化时触发的回调 partial void OnDungeonDetailsChanged(Dungeon value) { // 匹配Tier选项 SelectedTier = TierList.FirstOrDefault(t => t.Tier == value.level); // 匹配Enchant选项 SelectedEnchant = EnchantList.FirstOrDefault(e => e.Enchant == value.enchant); } // 直接用ObservableProperty生成选中项属性,无需手动包装 [ObservableProperty] private DungeonTier _selectedTier; [ObservableProperty] private DungeonEnchant _selectedEnchant;
同时修改Xaml绑定,直接绑定到自动生成的属性:
<Picker ItemsSource="{Binding TierList}" ItemDisplayBinding="{Binding Tier}" SelectedItem="{Binding SelectedTier}" /> <Picker ItemsSource="{Binding EnchantList}" ItemDisplayBinding="{Binding Enchant}" SelectedItem="{Binding SelectedEnchant}" />
步骤3:修正UpdateDungeon方法中的赋值
直接使用自动生成的选中项属性更新数据:
[RelayCommand] public async void UpdateDungeon() { if (SelectedEnchant != null) DungeonDetails.enchant = SelectedEnchant.Enchant; if (SelectedTier != null) DungeonDetails.level = SelectedTier.Tier; int response = -1; if (DungeonDetails.id > 0) { response = await _service.UpdateItemAsync(DungeonDetails); } await Shell.Current.Navigation.PopAsync(); }
额外优化:避免传递空对象
在DungeonDisplayViewModel的UpdateDungeon方法中,先判断选中对象是否为空:
[RelayCommand] public async void UpdateDungeon() { if(selectedDungeon == null) return; Dictionary<string, object> newParm = new Dictionary<string, object>(); newParm.Add("DungeonDetails", selectedDungeon); await AppShell.Current.GoToAsync(nameof(UpdateDungeonPage), newParm); }
内容的提问来源于stack exchange,提问作者Rafalekvx
相关产品推荐
相关产品推荐

