返回

.NET MAUI 动态添加 CheckBox (最多4个)

windows

.NET MAUI 根据数据动态添加最多四个 CheckBox 的方法

开发 .NET MAUI 应用时,有时需要根据数据动态显示多个 CheckBox。 比如,从数据库获取选项,然后把这些选项展示成复选框。如果选项数量不固定(但最多四个),直接写死在 XAML 里就不合适了。 这篇文章讲的就是如何实现这个功能。

问题

我们需要在页面的一部分区域内,动态显示 1 到 4 个 CheckBox。 CheckBox 的数量和对应的文字都从数据库获取。

假设我们有这样的数据结构:

public class CheckBoxItem
{
    public int Id { get; set; }
    public string Description { get; set; }
}

如果从数据库拿到了两个选项的数据:

var items = new List<CheckBoxItem>
{
    new CheckBoxItem { Id = 1, Description = "Option 1" },
    new CheckBoxItem { Id = 2, Description = "Option 2" }
};

那么页面上就应该显示两个 CheckBox,并且每个 CheckBox 旁边有对应的描述文字 ("Option 1" 和 "Option 2")。

为什么会出现问题?

直接把 CheckBox 写死在 XAML 里面,只适合数量和内容固定的情况。 如果数据是从数据库动态读取的,就必须用代码或者数据绑定的方式来动态生成 CheckBox。如果使用 StackLayout 直接和 ObservableCollection 进行绑定,虽然理论上可以更新,但往往因为没有正确的绑定或者通知机制,导致界面不刷新。

解决方案

下面介绍几种可行的解决方案, 并且逐步优化,解决可能的问题。

1. 代码生成 (Code-Behind)

这是最直接的方式。在页面的代码文件(code-behind)里,用 C# 代码创建 CheckBox,并添加到布局中。

原理:

  1. 获取数据。
  2. 清空布局(如果有旧的 CheckBox)。
  3. 循环数据,为每个数据项创建一个 CheckBox 实例。
  4. 设置 CheckBox 的相关属性(比如文字)。
  5. 将 CheckBox 添加到布局容器中(例如 StackLayout 或 Grid)。

代码示例:

假设页面上有一个 StackLayout,名叫 checkboxContainer

//XAML:  <StackLayout x:Name="checkboxContainer" />

private void CreateCheckBoxes(List<CheckBoxItem> items)
{
    checkboxContainer.Children.Clear(); // 清空旧的

    foreach (var item in items)
    {
        var checkBox = new CheckBox();
        var label = new Label { Text = item.Description };

        var horizontalLayout = new StackLayout
        {
            Orientation = StackOrientation.Horizontal,
             Children = { checkBox, label }
        };

        checkboxContainer.Children.Add(horizontalLayout);
    }
}

//调用方法:
// List<CheckBoxItem> items = GetCheckBoxItemsFromDatabase(); //假设这个函数从数据库获取数据
// CreateCheckBoxes(items);

优点: 简单直接,容易理解。

缺点: 界面和逻辑混在一起,代码不够整洁,可维护性差。 不符合 MVVM 模式。

2. 使用 Data Binding 和 DataTemplate

这是更推荐的做法,尤其是在使用 MVVM(Model-View-ViewModel)模式时。

原理:

  1. 创建一个 ViewModel,其中包含一个 ObservableCollection 属性,用于存放 CheckBoxItem 数据。
  2. 在 XAML 中,使用 BindableLayout.ItemsSource 属性将布局(例如 StackLayout)绑定到 ViewModel 的 ObservableCollection。
  3. 使用 BindableLayout.ItemTemplate 定义一个 DataTemplate,用于描述如何将每个 CheckBoxItem 对象渲染成 CheckBox。

代码示例:
首先创建一个 ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    private ObservableCollection<CheckBoxItem> _checkBoxItems;

    public ObservableCollection<CheckBoxItem> CheckBoxItems
    {
        get => _checkBoxItems;
        set
        {
            _checkBoxItems = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    public void LoadData()
    {
        // 模拟从数据库加载数据.
         var items = new List<CheckBoxItem>
        {
            new CheckBoxItem { Id = 1, Description = "Option 1" },
            new CheckBoxItem { Id = 2, Description = "Option 2" },
             new CheckBoxItem { Id = 3, Description = "选项 3"}
        };

         CheckBoxItems = new ObservableCollection<CheckBoxItem>(items);
    }

}

接着,修改XAML文件:

<StackLayout x:Name="checkboxContainer">
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <HorizontalStackLayout>
                <CheckBox />
                <Label Text="{Binding Description}" VerticalOptions="Center" />
            </HorizontalStackLayout>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

最后, 在页面的构造函数或者适当的时机,设置 BindingContext, 并加载数据。

public partial class MyPage : ContentPage
{
    private readonly MyViewModel _viewModel;
    public MyPage()
    {
        InitializeComponent();
        _viewModel = new MyViewModel();
        BindingContext = _viewModel;
        checkboxContainer.SetBinding(BindableLayout.ItemsSourceProperty, "CheckBoxItems");

        // 最好在页面显示的时候才加载数据,或者用一个按钮触发加载.
        _viewModel.LoadData();
    }
}

优点:

  • 代码清晰,界面和逻辑分离,符合 MVVM 模式。
  • 数据绑定,当数据变化时,界面会自动更新。
  • 容易维护和扩展。

缺点:

  • 需要额外创建 ViewModel.

3. 使用 CollectionView (更灵活)

如果 CheckBox 的布局更复杂,或者需要排序、分组、筛选等功能,可以使用 CollectionView。

原理:

CollectionView 是一个更强大的控件,专门用于显示列表数据。它的用法和 BindableLayout 类似,也需要绑定数据源和设置 ItemTemplate。

代码示例:

<CollectionView ItemsSource="{Binding CheckBoxItems}">
     <CollectionView.ItemsLayout>
         <LinearItemsLayout Orientation="Vertical" />
    </CollectionView.ItemsLayout>
    <CollectionView.ItemTemplate>
        <DataTemplate>
          <HorizontalStackLayout>
                <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
                <Label Text="{Binding Description}" VerticalOptions="Center"/>
            </HorizontalStackLayout>
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

ViewModel 中增加 IsChecked 属性:

public class CheckBoxItem : INotifyPropertyChanged
{
    public int Id { get; set; }
    public string Description { get; set; }

     private bool _isChecked;
    public bool IsChecked
    {
        get { return _isChecked; }
        set
        {
            if (_isChecked != value)
            {
                _isChecked = value;
                OnPropertyChanged();
                // 这里可以添加处理选中状态变化的逻辑
            }
        }
    }
     public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

}

注意: 如果 CheckBox 还需要处理选中状态变化, 需要在 CheckboxItem 类里增加 IsChecked 属性,实现 INotifyPropertyChanged接口, 然后在 XAML 中进行双向绑定( Mode=TwoWay).

优点:

  • 比BindableLayout更强大, 可以做更多的事情。
  • 对于处理 Checkbox 的状态很方便。

缺点: 比起简单的需求, CollectionView 稍显复杂。

4. 使用 Handler 定制 (进阶)

如果以上方案仍不能满足需求,例如你需要对 CheckBox 的外观或行为进行更深度的定制, 你可以考虑使用 Handler。 Handler 是 .NET MAUI 中连接平台原生控件和抽象控件的桥梁。

由于篇幅限制, 这里不再详细展开。可以参考官方文档了解更多 Handler 相关的内容。

安全建议

  • 数据校验: 始终对从数据库获取的数据进行校验, 确保数据的完整性和合法性. 例如, 保证 Description 不为空或过长.
  • 限制数量 : 由于明确说明数量在 1~4 个, 因此在ViewModel 或数据获取逻辑里, 就应该限制好, 不允许超出此范围。
  • 异常处理: 添加适当的异常处理,例如数据库连接失败、数据格式错误等。

以上几种方法, 从简到难, 都能解决动态显示 Checkbox 的需求, 实际开发选择哪一种, 看具体需要来定. 使用 BindableLayout 或者 CollectionView 加上 Data Binding,通常是最佳选择.