.NET MAUI MVVM中基于BottomSheet参数的CollectionView过滤异常
.NET MAUI MVVM CollectionView 过滤问题修正方案
核心问题分析
- BottomSheet 绑定上下文缺失:打开BottomSheet时未设置其BindingContext,导致
FiltrationCommand无法关联到主页面的ViewModel,过滤逻辑未实际执行 - 过滤逻辑缺陷:
- 直接对
Products集合做逐个Add/Remove操作,效率低且易出现集合状态不一致 - 文本搜索仅单次有效,是因为第一次过滤后
Products被修改,后续过滤未基于原始数据源重置 NotifyPropertyChanged()未明确指定属性名,UI无法准确感知Products集合的变化
- 直接对
- 同步HTTP请求阻塞UI:使用
.Result同步获取数据会卡住UI线程,影响用户体验
分步修正方案
1. 修复BottomSheet绑定上下文
在打开BottomSheet的方法中,将其BindingContext设置为当前ViewModel:
private void ToBottom(object obj) { BtmSheet btmSheet = new BtmSheet(); // 关键:绑定到当前ViewModel btmSheet.BindingContext = this; btmSheet.ShowAsync(); }
2. 重构过滤逻辑
每次过滤前基于原始数据源Prodyct重置Products,直接赋值过滤后的集合,避免逐个操作元素:
private void OnFiltration(object obj) { var searchM = obj as Manufacturer; if (searchM == null) return; // 基于原始数据过滤后直接赋值,自动触发PropertyChanged Products = new ObservableCollection<Product>( Prodyct.Where(c => c.manufacturer.Contains(searchM.Title, StringComparison.OrdinalIgnoreCase)) ); } private void OnChanged(object obj) { var searchText = obj?.ToString() ?? string.Empty; // 重置为原始数据后过滤,解决单次有效问题 Products = new ObservableCollection<Product>( Prodyct.Where(contact => contact.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)) ); }
3. 替换同步HTTP请求为异步
添加异步初始化方法,避免阻塞UI线程:
public noSfFiltration(INavigation _navigation) { navigation = _navigation; filtrationCommand = new Command<object>(OnFiltration); textChanged = new Command<object>(OnChanged); bottomMenu = new Command<object>(ToBottom); tapCommand = new Command<object>(OnTapped); // 调用异步初始化方法 _ = InitializeDataAsync(); } private async Task InitializeDataAsync() { // 异步获取数据,避免阻塞UI Prodyct = await httpClient.GetFromJsonAsync<ObservableCollection<Product>>("http://10.0.2.2:5192/api/Product"); Products = new ObservableCollection<Product>(Prodyct); manufacturers = await httpClient.GetFromJsonAsync<ObservableCollection<Manufacturer>>("http://10.0.2.2:5192/api/Manufacturer"); Manufacturers = new ObservableCollection<Manufacturer>(manufacturers); }
4. 修正属性通知逻辑
确保NotifyPropertyChanged正确触发,原代码的CallerMemberName已正确配置,保留即可:
public ObservableCollection<Product> Products { get { return _Products; } set { if (_Products != value) { _Products = value; NotifyPropertyChanged(); // CallerMemberName自动传入属性名"Products" } } }
完整修正后的ViewModel代码
public class noSfFiltration : INotifyPropertyChanged { private Command<object> tapCommand; private Command<object> textChanged; private Command<object> bottomMenu; private Command<object> filtrationCommand; public ObservableCollection<Product> Prodyct; private ObservableCollection<Product> _Products; private ObservableCollection<Manufacturer> _Manufacturers; public ObservableCollection<Manufacturer> manufacturers; private INavigation navigation; static HttpClient httpClient = new HttpClient(); public ObservableCollection<Product> Products { get { return _Products; } set { if (_Products != value) { _Products = value; NotifyPropertyChanged(); } } } public ObservableCollection<Manufacturer> Manufacturers { get { return _Manufacturers; } set { if (_Manufacturers != value) { _Manufacturers = value; NotifyPropertyChanged(); } } } public Command<object> FiltrationCommand { get { return filtrationCommand; } set { filtrationCommand = value; } } public Command<object> BottomMenu { get { return bottomMenu; } set { bottomMenu = value; } } public Command<object> TapCommand { get { return tapCommand; } set { tapCommand = value; } } public INavigation Navigation { get { return navigation; } set { navigation = value; } } public Command<object> TextChanged { get { return textChanged; } set { textChanged = value; } } public noSfFiltration(INavigation _navigation) { navigation = _navigation; filtrationCommand = new Command<object>(OnFiltration); textChanged = new Command<object>(OnChanged); bottomMenu = new Command<object>(ToBottom); tapCommand = new Command<object>(OnTapped); _ = InitializeDataAsync(); } private async Task InitializeDataAsync() { Prodyct = await httpClient.GetFromJsonAsync<ObservableCollection<Product>>("http://10.0.2.2:5192/api/Product"); Products = new ObservableCollection<Product>(Prodyct); manufacturers = await httpClient.GetFromJsonAsync<ObservableCollection<Manufacturer>>("http://10.0.2.2:5192/api/Manufacturer"); Manufacturers = new ObservableCollection<Manufacturer>(manufacturers); } private void ToBottom(object obj) { BtmSheet btmSheet = new BtmSheet(); btmSheet.BindingContext = this; btmSheet.ShowAsync(); } private void OnTapped(object obj) { var newPage = new ProductPage(); newPage.BindingContext = obj; Navigation.PushAsync(newPage); } private void OnFiltration(object obj) { var searchM = obj as Manufacturer; if (searchM == null) return; Products = new ObservableCollection<Product>( Prodyct.Where(c => c.manufacturer.Contains(searchM.Title, StringComparison.OrdinalIgnoreCase)) ); } private void OnChanged(object obj) { var searchText = obj?.ToString() ?? string.Empty; Products = new ObservableCollection<Product>( Prodyct.Where(contact => contact.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)) ); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
额外优化建议
- 添加搜索文本为空时重置为全部数据的逻辑:
private void OnChanged(object obj) { var searchText = obj?.ToString() ?? string.Empty; if (string.IsNullOrWhiteSpace(searchText)) { Products = new ObservableCollection<Product>(Prodyct); return; } Products = new ObservableCollection<Product>( Prodyct.Where(contact => contact.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)) ); } - 为
HttpClient添加生命周期管理,避免重复创建实例
内容的提问来源于stack exchange,提问作者vhhs
相关产品推荐
相关产品推荐

