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

如何用CommunityToolkit MVVM实现.NET MAUI SearchBar的TextChanged

.NET MAUI CommunityToolkit MVVM实现SearchBar搜索功能报错解决

问题说明

在.NET MAUI项目中使用CommunityToolkit MVVM框架,绑定CollectionView和SearchBar到ViewModel时,实现TextChanged功能遇到报错:

Event "Textchanged" can only be bound to properties of delegate type 'EventHandler'1'

原ViewModel代码

public partial class CommunityPostsPagesViewModel : ObservableObject
{
    private readonly ISecureStorageService _secureStorage;
    private readonly IAppSettingGeneratorService _appsettingGenerator;
    private readonly ICommunityPostCoreApiService _communitypostService;
    private readonly IClientCryptoGraphy _cryptoGraphy;

    public CommunityPostsPagesViewModel(ISecureStorageService storage, ICommunityPostCoreApiService communitypostService
        , IAppSettingGeneratorService appsettingGenerator, IClientCryptoGraphy cryptoGraphy)
    {
        _secureStorage = storage;
        _communitypostService = communitypostService;
        _appsettingGenerator = appsettingGenerator;
        _cryptoGraphy = cryptoGraphy;
        GetLastestComunityPost();
    }

    [ObservableProperty] 
    string serchText;

    [ObservableProperty]
    ObservableCollection<ComunityPostsModel> comunityPosts;

    [RelayCommand]
    async void SearchTextChanged()
    {
        ComunityPosts.Clear();
        var output = new ObservableCollection<ComunityPostsModel>();
        try
        {
            var apiResponse = await _communitypostService.GetAll();
            var searchResponse = apiResponse.Response.Where(m => m.Title.ToLower().Contains(SerchText.ToLower()));
            if (!searchResponse.Any())
            {
                await Shell.Current.DisplayAlert("Oops!", $"There are currently no available community post with name: {SerchText}", "Ok");
                return;
            }
            else
            {
                foreach (var response in searchResponse)
                {
                    output.Add(new ComunityPostsModel()
                    {
                        DatePosted = response.DatePosted,
                        Body = response.Body,
                        Id = response.Id,
                        Title = response.Title
                    });
                }
            }
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("Oops!", "Could not get any comunity Post", "Ok");
        }
        ComunityPosts = output;
    }

    [RelayCommand]
    async void ComunityPostSelected(ComunityPostsModel input)
    {
        await Shell.Current.GoToAsync(nameof(CommunityPostPage), true, new Dictionary<string, object>{
            {nameof(ComunityPostsModel),input}
        });
    }

    private async Task GetLastestComunityPost()
    {
        var output = new ObservableCollection<ComunityPostsModel>();
        try
        {
            var apiResponse = await _communitypostService.GetAll();
            foreach (var response in apiResponse.Response)
            {
                output.Add(new ComunityPostsModel()
                {
                    DatePosted = response.DatePosted,
                    Body = response.Body,
                    Id = response.Id,
                    Title = response.Title
                });
            }
        }
        catch (Exception ex)
        {
        }
        ComunityPosts = output;
    }
}

原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="AE.Pages.CommunityPostsPage"
    Title="Community Beta"
    xmlns:viewmodel ="clr-namespace:AE.ViewModels"
    x:DataType="viewmodel:CommunityPostsPagesViewModel"
    xmlns:itemViewModel ="clr-namespace:AE.Models"
    xmlns:ur="http://schemas.enisn-projects.io/dotnet/maui/uraniumui/material"
    >
    <StackLayout Orientation="Vertical" Margin="25" Padding="25">
        <StackLayout>
            <SearchBar SearchCommand="{Binding SearchTextChangedCommand}" 
                       Placeholder="Search Comunity Posts by Title" Text=" {Binding SerchText}" 
                       IsSpellCheckEnabled="True" TextChanged="{Binding SearchTextChangedCommand}" Margin="00,00,00,25"/>
            <CollectionView ItemsSource="{Binding ComunityPosts}" >
                <CollectionView.ItemTemplate >
                    <DataTemplate x:DataType="{x:Type itemViewModel:ComunityPostsModel}" >
                        <SwipeView>
                            <Frame>
                                <Frame.GestureRecognizers>
                                    <TapGestureRecognizer Command="{Binding Source= {RelativeSource AncestorType={x:Type viewmodel:CommunityPostsPagesViewModel}}, Path=ComunityPostSelectedCommand}" CommandParameter="{Binding .}" />
                                </Frame.GestureRecognizers>
                                <StackLayout>
                                    <Label Text="{Binding Title}" FontFamily="Bold"/>
                                    <Label Text="{Binding DatePosted}" FontSize="Caption"/>
                                </StackLayout>
                            </Frame>
                        </SwipeView>
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </StackLayout>
    </StackLayout>
</ContentPage>

错误原因

TextChanged是SearchBar的事件,不能直接绑定RelayCommand(命令类型)。同时绑定SearchCommand和TextChanged到同一个命令属于用法错误:SearchCommand是SearchBar专门触发搜索的命令属性,而TextChanged是事件,需要通过事件绑定或属性变更回调处理。

解决方案

方案一:利用ObservableProperty属性变更回调(推荐,更简洁)

借助CommunityToolkit MVVM的ObservableProperty自动生成的属性变更方法,在搜索文本变化时直接触发搜索逻辑:

  1. 修改ViewModel:

    public partial class CommunityPostsPagesViewModel : ObservableObject
    {
        // 保留原有依赖注入和构造函数,此处省略重复代码
    
        // 修正拼写错误:SerchText → SearchText
        [ObservableProperty] 
        string searchText;
    
        [ObservableProperty]
        ObservableCollection<ComunityPostsModel> comunityPosts;
    
        // 缓存原始数据,避免每次搜索都调用API
        private List<ComunityPostsModel> _originalPosts;
    
        private async Task GetLastestComunityPost()
        {
            var output = new ObservableCollection<ComunityPostsModel>();
            try
            {
                var apiResponse = await _communitypostService.GetAll();
                _originalPosts = apiResponse.Response.Select(response => new ComunityPostsModel
                {
                    DatePosted = response.DatePosted,
                    Body = response.Body,
                    Id = response.Id,
                    Title = response.Title
                }).ToList();
                foreach (var post in _originalPosts)
                {
                    output.Add(post);
                }
            }
            catch (Exception ex)
            {
                await Shell.Current.DisplayAlert("提示!", "无法获取社区帖子", "确定");
            }
            ComunityPosts = output;
        }
    
        // CommunityToolkit自动生成的属性变更回调方法
        partial void OnSearchTextChanged(string value)
        {
            // 防抖处理:延迟300ms执行搜索,避免频繁触发
            _ = Task.Delay(300).ContinueWith(_ => PerformSearch(value), TaskScheduler.FromCurrentSynchronizationContext());
        }
    
        // 独立的搜索逻辑方法
        private void PerformSearch(string searchValue)
        {
            if (_originalPosts == null || !_originalPosts.Any())
                return;
    
            var filteredPosts = string.IsNullOrWhiteSpace(searchValue)
                ? _originalPosts
                : _originalPosts.Where(m => m.Title.ToLower().Contains(searchValue.ToLower())).ToList();
    
            if (!filteredPosts.Any() && !string.IsNullOrWhiteSpace(searchValue))
            {
                _ = Shell.Current.DisplayAlert("提示!", $"当前没有标题包含「{searchValue}」的社区帖子", "确定");
            }
    
            ComunityPosts = new ObservableCollection<ComunityPostsModel>(filteredPosts);
        }
    
        // 保留原有ComunityPostSelected命令,此处省略重复代码
    }
    
  2. 修改XAML:
    只需要绑定搜索文本,移除错误的SearchCommand和TextChanged绑定:

    <SearchBar 
        Placeholder="按标题搜索社区帖子" 
        Text="{Binding SearchText}" 
        IsSpellCheckEnabled="True" 
        Margin="0,0,0,25"/>
    

方案二:使用EventToCommandBehavior绑定TextChanged事件

如果需要通过命令绑定事件,使用CommunityToolkit.Maui提供的EventToCommandBehavior:

  1. 安装CommunityToolkit.Maui包:

    dotnet add package CommunityToolkit.Maui
    
  2. 添加命名空间到XAML:

    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
    
  3. 创建TextChanged事件参数转换器:

    public class TextChangedEventArgsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is TextChangedEventArgs args)
            {
                return args.NewTextValue;
            }
            return string.Empty;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
  4. 在XAML中声明转换器资源:

    <ContentPage.Resources>
        <itemViewModel:TextChangedEventArgsConverter x:Key="TextChangedEventArgsConverter"/>
    </ContentPage.Resources>
    
  5. 修改SearchBar的XAML代码:

    <SearchBar 
        Placeholder="按标题搜索社区帖子" 
        Text="{Binding SearchText}" 
        IsSpellCheckEnabled="True" 
        Margin="0,0,0,25">
        <SearchBar.Behaviors>
            <toolkit:EventToCommandBehavior
                EventName="TextChanged"
                Command="{Binding SearchTextChangedCommand}"
                EventArgsConverter="{StaticResource TextChangedEventArgsConverter}"/>
        </SearchBar.Behaviors>
    </SearchBar>
    
  6. 修改ViewModel的RelayCommand:

    [RelayCommand]
    async void SearchTextChanged(string searchValue)
    {
        // 复用方案一中的PerformSearch逻辑
        PerformSearch(searchValue);
    }
    

额外优化建议

  • 缓存原始数据:首次加载时把API返回的数据缓存到ViewModel私有字段,搜索时直接从缓存过滤,减少不必要的API请求。
  • 添加防抖逻辑:延迟触发搜索,避免用户输入过程中频繁执行搜索操作。
  • 修正拼写错误:统一变量命名(如SerchText改为SearchText),避免后续逻辑出错。

内容的提问来源于stack exchange,提问作者Rikudou En Sof

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 01:42:06