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

清除ViewModel中ObservableCollection后CollectionView不再更新

MAUI中CollectionView切换选中项后视图不更新的问题

问题场景

从MainPage导航至SecondPage,首次选择列表项时,右侧网格能正常加载对应属性;但切换选择另一项后,虽然已清空并重新填充属性集合,右侧的CollectionView视图却不再更新。

预期效果

选择新的列表项后,视图能同步更新显示对应的属性信息。

效果截图

  • 选择Data1时:选择Data1
  • 选择Data2时:选择Data2

相关代码

Model.cs

using CommunityToolkit.Mvvm.ComponentModel;

namespace Model;

public class Data : ObservableObject
{
    public string Number {get;set;}

    public string Group {get;set;}

    public string DateTime {get;set;}

    public Data(){}

    public Data(List<object> objList)
    {
        int i=0;
        foreach (System.Reflection.PropertyInfo property in typeof(Data).GetProperties().ToList())
        {           
            property.SetValue(this, objList[i]);
            i++;            
        }
    }   
}

public class PropertiesList : ObservableObject
{
    public string Key {get;set;}

    public string Value {get;set;}

    public PropertiesList(string key, string value)
    {
        Key = key;
        Value = value;  
    }   
}

SecondPage.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"
             x:Class="Test.SecondPage"
             xmlns:model="clr-namespace:Model"
             xmlns:viewmodel="clr-namespace:Viewmodel"
             xmlns:collections="clr-namespace:CommunityToolkit.Mvvm.Collections;assembly=CommunityToolkit.Mvvm"             
             x:DataType="viewmodel:SecondPageViewModel">

  <ContentPage.Resources>
    <Style TargetType="Grid">
      <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
          <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal" />
            <VisualState x:Name="Selected">
              <VisualState.Setters>
                <Setter Property="BackgroundColor" Value="LightGray" />
              </VisualState.Setters>
            </VisualState>
          </VisualStateGroup>
        </VisualStateGroupList>
      </Setter>
    </Style>
  </ContentPage.Resources>

  <Grid Margin="20" RowSpacing="20" RowDefinitions="*,Auto" ColumnDefinitions="*">
    <Grid Grid.Row="0" Margin="20" RowSpacing="20" RowDefinitions="*" ColumnDefinitions="*,2*">
      <Frame Grid.Column="0" BorderColor="Gray" CornerRadius="10" HasShadow="True">
        <CollectionView ItemsSource="{Binding ObsDataList}" IsGrouped="True" SelectionMode="Single" SelectedItem="{Binding SelectedData, Mode=TwoWay}" SelectionChanged="OnDataSelectionChanged">
          <CollectionView.GroupHeaderTemplate>
            <DataTemplate x:DataType="collections:IReadOnlyObservableGroup">
              <Grid RowDefinitions="Auto" ColumnDefinitions="*">
                <Label Text="{Binding Key}" BackgroundColor="LightGray" FontSize="18" FontAttributes="Bold" />
              </Grid>
            </DataTemplate>
          </CollectionView.GroupHeaderTemplate>
          <CollectionView.ItemTemplate>
            <DataTemplate x:DataType="model:Data">
              <Grid Padding="10" RowDefinitions="Auto" ColumnDefinitions="Auto,Auto,Auto,*" ColumnSpacing="50">
                <Label Grid.Column="0" Text="{Binding Number}" FontSize="20" HorizontalOptions="Center" VerticalOptions="Center" FontAttributes="Bold"/>
                <VisualStateManager.VisualStateGroups>
                  <VisualStateGroup Name="CommonStates">
                    <VisualState Name="Selected">
                      <VisualState.Setters>
                        <Setter Property="BackgroundColor" Value="LightBlue" />
                      </VisualState.Setters>
                    </VisualState>
                  </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
              </Grid>
            </DataTemplate>
          </CollectionView.ItemTemplate>
        </CollectionView>
      </Frame>
      <Frame Grid.Column="1" BorderColor="Gray" CornerRadius="10" HasShadow="True">
        <Grid RowDefinitions="*" ColumnDefinitions="*">
          <CollectionView Grid.Column="0" ItemsSource="{Binding ObsDataProperties}" SelectionMode="None">
            <CollectionView.ItemTemplate>
              <DataTemplate x:DataType="model:PropertiesList">
                <Frame BorderColor="Gray" CornerRadius="10">
                  <Grid RowDefinitions="Auto" ColumnDefinitions="200,200" RowSpacing="2">
                    <Label Grid.Column="0" Text="{Binding Key}" FontSize="10" HorizontalOptions="Start" VerticalOptions="Center" FontAttributes="Bold"/>
                    <Label Grid.Column="1" Text="{Binding Value}" FontSize="10" HorizontalOptions="Start" VerticalOptions="Center" />
                  </Grid>
                </Frame>
              </DataTemplate>
            </CollectionView.ItemTemplate>
          </CollectionView>
        </Grid>
      </Frame>
    </Grid>
    <Frame Grid.Row="1" BorderColor="Gray" CornerRadius="10" HasShadow="True">
      <Grid RowDefinitions="Auto" ColumnDefinitions="*,*,*" ColumnSpacing="10">
        <Button TextColor="White" FontAutoScalingEnabled="True" Text="Clear" Command="{Binding ClearDataInfoCommand}" HeightRequest="33" HorizontalOptions="Start"/>
      </Grid>
    </Frame>
  </Grid>

</ContentPage>

SecondPage.xaml.cs

using Model;
using Viewmodel;

namespace Test;

public partial class SecondPage : ContentPage
{
    public SecondPage(SecondPageViewModel viewmodel)
    {
        InitializeComponent();
        BindingContext = viewmodel;
    }

    protected override void OnNavigatedTo(NavigatedToEventArgs args)
    {            
        ((SecondPageViewModel)this.BindingContext).GetDataCommand.Execute(null);                

        base.OnNavigatedTo(args);       
    }

    void OnDataSelectionChanged(object sender, SelectionChangedEventArgs e)
    {        
        if (e.CurrentSelection.Count() != 0) 
            ((SecondPageViewModel)this.BindingContext).ShowDataInfoCommand.Execute(e.CurrentSelection.FirstOrDefault() as Data);            
    }
}

SecondPageViewModel.cs

using Model;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Collections;
using System.Collections.ObjectModel;

namespace Viewmodel;

public partial class SecondPageViewModel : BaseViewModel
{
    private IEnumerable<IGrouping<string, Data>> groupedDataList;

    [ObservableProperty]
    public Data selectedData;

    public ObservableCollection<PropertiesList> ObsDataProperties {get;} = new();

    public ObservableGroupedCollection<string, Data> ObsDataList {get;}  = new();

    public SecondPageViewModel(IServiceProvider provider) : base(provider){} 

    [RelayCommand]
    public async Task GetDataAsync()
    {       
        if (ObsDataList.Count is not 0) ObsDataList.Clear();

        if (ObsDataProperties.Count is not 0) ObsDataProperties.Clear(); 

        await Task.Delay(0);

        List<Data> dataList = new List<Data>()
        {
            new Data
            (
                new List<object>
                {
                    "DATA1",
                    "GROUP1",                    
                    "DATETIME"
                }                               
            ),
            new Data
            (
                new List<object>
                {
                    "DATA2",
                    "GROUP2",
                    "DATETIME"   
                } 
            )        
      };

        groupedDataList =  dataList.GroupBy(data => data.Group);  

        foreach(var dataGroup in groupedDataList)
        {            
            ObsDataList.AddGroup(dataGroup);
        }       

    }

    [RelayCommand]
    public void ShowDataInfo(Data data)
    {      
        if (ObsDataProperties.Count is not 0) ObsDataProperties.Clear();       

        foreach (System.Reflection.PropertyInfo property in data.GetType().GetProperties())
        {
            ObsDataProperties.Add
            (
                new PropertiesList
                (
                    property.Name,
                    property.GetValue(data).ToString()
                )        
            );
        }       
    }

    [RelayCommand]
    public void ClearDataInfo()
    {     
        if (ObsDataProperties.Count is not 0) ObsDataProperties.Clear();

        SelectedData = null;
    }
}

问题原因及解决方案

原因分析

  1. 绑定冲突:同时使用SelectedItem双向绑定和SelectionChanged事件,易导致逻辑混乱,干扰视图更新流程。
  2. 集合更新方式:直接对固定的ObservableCollection执行Clear()+Add()操作,在部分MAUI场景下无法可靠触发视图更新通知。

解决方案步骤

1. 优化ViewModel属性与更新逻辑

将ObsDataProperties改为可观察属性,每次更新时重新创建集合,确保绑定系统感知变化:

using Model;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Collections;
using System.Collections.ObjectModel;

namespace Viewmodel;

public partial class SecondPageViewModel : BaseViewModel
{
    private IEnumerable<IGrouping<string, Data>> groupedDataList;

    [ObservableProperty]
    public Data selectedData;

    // 改为可观察属性,自动生成属性变更通知
    [ObservableProperty]
    private ObservableCollection<PropertiesList> obsDataProperties = new();

    public ObservableGroupedCollection<string, Data> ObsDataList {get;}  = new();

    public SecondPageViewModel(IServiceProvider provider) : base(provider){} 

    [RelayCommand]
    public async Task GetDataAsync()
    {       
        if (ObsDataList.Count is not 0) ObsDataList.Clear();

        ObsDataProperties = new ObservableCollection<PropertiesList>(); 

        await Task.Delay(0);

        List<Data> dataList = new List<Data>()
        {
            new Data
            (
                new List<object>
                {
                    "DATA1",
                    "GROUP1",                    
                    "DATETIME"
                }                               
            ),
            new Data
            (
                new List<object>
                {
                    "DATA2",
                    "GROUP2",
                    "DATETIME"   
                } 
            )        
      };

        groupedDataList =  dataList.GroupBy(data => data.Group);  

        foreach(var dataGroup in groupedDataList)
        {            
            ObsDataList.AddGroup(dataGroup);
        }       
    }

    [RelayCommand]
    public void ShowDataInfo(Data data)
    {      
        var newProperties = new ObservableCollection<PropertiesList>();
        foreach (System.Reflection.PropertyInfo property in data.GetType().GetProperties())
        {
            newProperties.Add
            (
                new PropertiesList
                (
                    property.Name,
                    property.GetValue(data).ToString()
                )        
            );
        }       
        ObsDataProperties = newProperties;
    }

    [RelayCommand]
    public void ClearDataInfo()
    {     
        ObsDataProperties = new ObservableCollection<PropertiesList>();
        SelectedData = null;
    }

    // 利用属性变更回调处理选中项切换逻辑
    partial void OnSelectedDataChanged(Data value)
    {
        if (value != null)
        {
            ShowDataInfoCommand.Execute(value);
        }
        else
        {
            ClearDataInfoCommand.Execute(null);
        }
    }
}

2. 移除XAML中的SelectionChanged事件绑定

修改SecondPage.xaml中的CollectionView,删除事件绑定:

<CollectionView ItemsSource="{Binding ObsDataList}" IsGrouped="True" SelectionMode="Single" SelectedItem="{Binding SelectedData, Mode=TwoWay}">

3. 删除代码后台的事件处理方法

移除SecondPage.xaml.cs中的OnDataSelectionChanged方法:

// 移除此方法
// void OnDataSelectionChanged(object sender, SelectionChangedEventArgs e)
// {        
//     if (e.CurrentSelection.Count() != 0) 
//         ((SecondPageViewModel)this.BindingContext).ShowDataInfoCommand.Execute(e.CurrentSelection.FirstOrDefault() as Data);            
// }

修改完成后,选中项变更会通过属性回调触发属性加载,每次更新集合时都会创建新的ObservableCollection,确保视图能同步更新。


内容的提问来源于stack exchange,提问作者tvarela

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 14:09:54