MonkeyHua

.Net MAUI实现MultiPicker

基本原理

主要基于官方提供的 Entry 控件和第三方提供的 Popup 控件,通过继承 ContentView 类,来实现自定义控件;其中多项选择功能通过内置的官方 CollectionView 控件来实现。

属性说明

可绑定属性 属性类型 描述
ItemsSource IEnumerable 绑定数据源
SelectedItems IList<object> 预选择项内容
Title string 标题
DisplayPath string 显示内容绑定属性名称

代码实现

using System.Collections;
using Syncfusion.Maui.Toolkit.Popup;

namespace Company.User.Controls
{
    public class MultiPicker : ContentView
    {
        public static readonly BindableProperty TitleProperty = BindableProperty.Create(
            nameof(Title), typeof(string), typeof(MultiPicker), defaultValue: null);

        public static readonly BindableProperty DisplayPathProperty = BindableProperty.Create(
            nameof(DisplayPath), typeof(string), typeof(MultiPicker), defaultValue: null, propertyChanged: OnDisplayPathChanged);

        public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(MultiPicker), null);

        public static readonly BindableProperty SelectedItemsProperty =
            BindableProperty.Create(nameof(SelectedItems), typeof(IList<object>), typeof(MultiPicker), null);

        public IEnumerable ItemsSource
        {
            get => (IEnumerable)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }

        public IList<object> SelectedItems
        {
            get => (IList<object>)GetValue(SelectedItemsProperty);
            set => SetValue(SelectedItemsProperty, value);
        }

        public string Title
        {
            get => (string)GetValue(TitleProperty);
            set => SetValue(TitleProperty, value);
        }

        public string DisplayPath
        {
            get => (string)GetValue(DisplayPathProperty);
            set => SetValue(DisplayPathProperty, value);
        }

        public MultiPicker()
        {
            var stack = new VerticalStackLayout()
            {
                Spacing = 10,
                Padding = new Thickness(10, 0),
                VerticalOptions = LayoutOptions.Center
            };
            _itemsEntry = new Entry()
            {
                IsReadOnly = true,
                VerticalOptions = LayoutOptions.Center
            };
            _itemsEntry.SetBinding(Entry.PlaceholderProperty, new Binding(nameof(Title), source: this));
            var tap = new TapGestureRecognizer();
            tap.Tapped += Entry_Tapped;
            _itemsEntry.GestureRecognizers.Add(tap);
            stack.Add(_itemsEntry);

            _popup = new SfPopup()
            {
                BackgroundColor = popBackColor,
                AppearanceMode = PopupButtonAppearanceMode.TwoButton,
                ShowFooter = true,
                AcceptButtonText = "确定",
                DeclineButtonText = "取消",
                AutoSizeMode = PopupAutoSizeMode.Height,
                ContentTemplate = new DataTemplate(() => CreatePopupContentTemplate())
            };
            _popup.SetBinding(SfPopup.HeaderTitleProperty, new Binding(nameof(Title), source: this));
            stack.Add(_popup);

            Content = stack;
        }

        private void Entry_Tapped(object? sender, TappedEventArgs e)
        {
            _popup.IsOpen = true;
        }

        private Border CreatePopupContentTemplate()
        {
            _collection = new CollectionView()
            {
                ItemsLayout = new LinearItemsLayout(ItemsLayoutOrientation.Vertical),
                SelectionMode = SelectionMode.Multiple,
                ItemTemplate = new DataTemplate(() => CreateItemTemplate())
            };
            _collection.SetBinding(CollectionView.ItemsSourceProperty, new Binding(nameof(ItemsSource), source: this));
            _collection.SetBinding(CollectionView.SelectedItemsProperty, new Binding(nameof(SelectedItems), source: this));
            _collection.SelectionChanged += _collection_SelectionChanged;

            var border = new Border()
            {
                Stroke = Colors.Black,
                Content=_collection
            }; ;

            return border;
        }

        private void _collection_SelectionChanged(object? sender, SelectionChangedEventArgs e)
        {
            string? items = null;
            var count = e.CurrentSelection.Count;
            if (count > 0)
            {
                 var first = e.CurrentSelection[0];
                 if (string.IsNullOrEmpty(DisplayPath))
                 {
                     items = first.ToString();
                 }
                 else
                 {
                     var property = first.GetType().GetProperty(DisplayPath);
                     var firstValue = property?.GetValue(first);
                     items = firstValue?.ToString();
                 }

                 if (count > 1)
                 {
                     items += $" +{count - 1}";
                 }
             }

            _itemsEntry.Text = items;
        }

        private Label CreateItemTemplate()
        {
            var label = new Label();
            label.Margin = new Thickness(6);
            label.SetBinding(Label.TextProperty, string.IsNullOrEmpty(DisplayPath) ? new Binding(".") : new Binding(DisplayPath));

            return label;
        }
        private static void OnDisplayPathChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var view = (MultiPicker)bindable;
            view.UpdateDisplayPath();
        }
        private void UpdateDisplayPath()
        {
            _collection.ItemTemplate = new DataTemplate(() => CreateItemTemplate());
        }

        private Entry _itemsEntry = new();
        private SfPopup _popup = new();
        private CollectionView _collection = new();
    }
}

注意事项:

  1. 引用Nuget包Syncfusion.Maui.Toolkit 1.0.7
  2. DisplayPathProperty默认值为空,表示绑定整个对象;
  3. 数据模板使用格式:new DataTemplate(() => CreateItemTemplate());

示例

Test.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="Company.User.Views.TestPage"
             xmlns:user="clr-namespace:Company.User.Controls"
             Title="测试多项选择">

    <user:MultiPicker Title="选择项目" 
                    ItemsSource="{Binding PopupItemsSource}" 
                    SelectedItems="{Binding SelectItems}" 
                    DisplayPath="Text"
                    VerticalOptions="Center" />

</ContentPage>

Test.xaml.cs

using System.Collections.ObjectModel;

namespace Company.User.Views;

public partial class TestPage : ContentPage
{
    public TestPage()
    {
        InitializeComponent();

        SetupPopup();
        this.BindingContext = this;
    }

    private ObservableCollection<PopupModelCollection> _popupItemsSource = new();
    public ObservableCollection<PopupModelCollection> PopupItemsSource
    {
        get => _popupItemsSource;
        set
        {
            _popupItemsSource = value;
            OnPropertyChanged(nameof(PopupItemsSource));
        }
    }

    private List<object> _selectItems = new();
    public List<object> SelectItems
    {
        get => _selectItems;
        set
        {
            _selectItems = value;
            OnPropertyChanged(nameof(SelectItems));
        }
    }


    void SetupPopup()
    {
        var collection = new List<PopupModelCollection>();
        for (int i = 0; i < 15; i++)
        {
            PopupModelCollection model = new PopupModelCollection
            {
                Id = i,
                Text = $"Item{i}"
            };
            collection.Add(model);
        }

        PopupItemsSource = new(collection);
        SelectItems = new(collection.Take(3));
    }

    public class PopupModelCollection
    {
        public int Id { get; set; }
        public string? Text { get; set; }

    }
}

截图(基于安卓平台)

  1. 主页面截图
    主页面截图
  2. 弹窗截图
    弹窗截图

参考素材

  1. .Net MAUI Controls API文档
  2. The Syncfusion® Toolkit for .NET MAUI API文档

posted on 2026-03-31 14:05  MonkeyHua  阅读(7)  评论(0)    收藏  举报

导航