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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 23:27:34