.NET MAUI实现可输入可选择下拉控件的方案咨询
完善.NET MAUI 自定义可输入Picker与数据保存加载方案
一、自定义可输入Picker控件优化
你已经基于Community Toolkit Popup和Entry+按钮搭建了基础结构,这里补充关键优化点:
1. 控件绑定逻辑完善
在CustomPicker的XAML中,把Entry的Text绑定到ViewModel的SelectedName属性,下拉按钮绑定触发Popup的命令:
<HorizontalStackLayout> <Entry x:Name="InputEntry" Text="{Binding SelectedName, Mode=TwoWay}" Placeholder="输入或选择名称"/> <Button x:Name="DropDownBtn" Text="▼" Command="{Binding ShowPopupCommand}" WidthRequest="30"/> </HorizontalStackLayout>
在ViewModel中实现弹窗触发与选中回调:
public ICommand ShowPopupCommand => new Command(async () => { var popup = new CustomSelectionPopup(AvailableNames, SelectedName); popup.OnItemSelected += (s, selectedName) => { SelectedName = selectedName; InputEntry.Text = selectedName; }; await PopupExtensions.ShowPopupAsync(this, popup); });
2. Popup内列表与输入过滤
在CustomPopup中添加过滤用Entry,实时筛选下拉选项:
<VerticalStackLayout Padding="10" WidthRequest="250" HeightRequest="300"> <Entry Placeholder="搜索名称" Text="{Binding FilterText, Mode=TwoWay}"/> <CollectionView ItemsSource="{Binding FilteredNames}" SelectionMode="Single" SelectionChangedCommand="{Binding SelectItemCommand}"> <CollectionView.ItemTemplate> <DataTemplate> <Label Text="{Binding}"/> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </VerticalStackLayout>
Popup的ViewModel实现过滤逻辑:
private string _filterText; public string FilterText { get => _filterText; set { SetProperty(ref _filterText, value); FilteredNames = new ObservableRangeCollection<string>( OriginalNames.Where(n => n.Contains(value, StringComparison.OrdinalIgnoreCase))); } } public ObservableRangeCollection<string> OriginalNames { get; } public ObservableRangeCollection<string> FilteredNames { get; private set; } public ICommand SelectItemCommand => new Command<string>(selectedName => { OnItemSelected?.Invoke(this, selectedName); PopupExtensions.ClosePopupAsync(this); }); public event EventHandler<string> OnItemSelected;
二、适配你的集合结构
针对ObservableRangeCollection<Dictionary<string, ObservableRangeCollection<ItemModel>>>结构,在ViewModel中维护下拉选项集合,监听源集合变化自动更新:
private ObservableRangeCollection<Dictionary<string, ObservableRangeCollection<ItemModel>>> _sourceCollections; public ObservableRangeCollection<Dictionary<string, ObservableRangeCollection<ItemModel>>> SourceCollections { get => _sourceCollections; set { SetProperty(ref _sourceCollections, value); UpdateAvailableNames(); value.CollectionChanged += (s, e) => UpdateAvailableNames(); } } private void UpdateAvailableNames() { AvailableNames.Clear(); foreach (var dict in SourceCollections) { AvailableNames.AddRange(dict.Keys); } } public ObservableRangeCollection<string> AvailableNames { get; } = new();
三、保存与加载逻辑实现
1. 保存逻辑
点击保存按钮时,将当前CollectionView的Item集合与选中/输入的名称关联,存入字典后序列化到本地:
public ICommand SaveCommand => new Command(() => { var existingDict = SourceCollections.FirstOrDefault(d => d.ContainsKey(SelectedName)); if (existingDict != null) { existingDict[SelectedName] = CurrentItemCollection; } else { SourceCollections.Add(new Dictionary<string, ObservableRangeCollection<ItemModel>> { { SelectedName, CurrentItemCollection } }); } var json = JsonSerializer.Serialize(SourceCollections); File.WriteAllText(Path.Combine(FileSystem.AppDataDirectory, "saved_data.json"), json); }); public ObservableRangeCollection<ItemModel> CurrentItemCollection { get; } = new();
2. 加载逻辑
在ViewModel初始化时读取本地保存的数据:
public MainViewModel() { LoadSavedData(); } private void LoadSavedData() { var filePath = Path.Combine(FileSystem.AppDataDirectory, "saved_data.json"); if (File.Exists(filePath)) { var json = File.ReadAllText(filePath); var loadedCollections = JsonSerializer.Deserialize<ObservableRangeCollection<Dictionary<string, ObservableRangeCollection<ItemModel>>>>(json); if (loadedCollections != null) { SourceCollections = loadedCollections; } } }
四、空间优化建议
- 紧凑布局:将CustomPicker的Entry和按钮用
HorizontalStackLayout紧密排列,按钮改用图标(如FontAwesome下拉箭头)代替文字,减少宽度占用。 - 弹窗高度限制:设置Popup的
HeightRequest为固定值(如250),开启CollectionView的滚动,避免弹窗占用过多空间。 - 自动关闭弹窗:用户选中选项或在Entry中按下回车键时,自动关闭Popup,提升操作流畅度。
内容的提问来源于stack exchange,提问作者OXO
相关产品推荐
相关产品推荐

