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

.NET Maui:如何在BindableLayout中正确分组RadioButton?

.NET Maui RadioButton 子列表选择问题

我正在开发一个.NET Maui应用,需要用RadioButton让用户从父项的子列表中选择项,结构是Item1(SubItem1-4)、Item2(SubItem1-4)等,所有父项的子项数据源相同。我用BindableLayout和ContentView生成视图,需求是每个父项仅能选一个子项,选中值存在绑定属性SelectedItem里。

目前遇到两个问题:

  • 选择Item1的SubItem1时,Item2、Item3、Item4的选中项也同步变成SubItem1;
  • 给RadioButton绑定不同GroupName后,RadioButton_CheckedChanged事件会触发4次,需要避免多次调用。

主页面XAML代码

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MauiSampleApp.Views.RadioButtonPage"
    xmlns:controls="clr-namespace:MauiSampleApp.Controls"
    Title="RadioButtonPage">
    <VerticalStackLayout
        Spacing="10">
        <VerticalStackLayout
            BindableLayout.ItemsSource="{Binding ModelItemList}">
            <BindableLayout.ItemTemplate>
                <DataTemplate>
                    <Grid
                        RowSpacing="6"
                        RowDefinitions="20,auto">

                        <Label
                            Grid.Row="0"
                            Text="{Binding Title}"/>

                        <controls:RadioButtonContentView
                            Grid.Row="1"
                            GroupName="{Binding Title}"
                            ItemSource="{Binding ItemList}"/>
                    </Grid>
                </DataTemplate>
            </BindableLayout.ItemTemplate>
        </VerticalStackLayout>
    </VerticalStackLayout>
</ContentPage>

主页面后端代码

public partial class RadioButtonPage : ContentPage
{
    private RadioButtonViewModel viewModel { get; set; }
    public RadioButtonPage()
    {
    InitializeComponent();
        BindingContext = viewModel = new RadioButtonViewModel();
    }
}

主页面ViewModel代码

public class RadioButtonViewModel : ObservableObject
{
    private List<ModelClass> _modelItemList = new();
    public List<ModelClass> ModelItemList
    {
        get => _modelItemList;
        set => SetProperty(ref _modelItemList, value);
    }

    private PickerItem _selectedItem;
    public PickerItem SelectedItem
    {
        get => _selectedItem;
        set => SetProperty(ref _selectedItem, value);
    }

    public List<PickerItem> RadioButtonList { get; set; }

    public RadioButtonViewModel()
    {
        RadioButtonList = new()
        {
            new PickerItem { Key = "SubItem1", IsSelected = true },
            new PickerItem { Key = "SubItem2", IsSelected = false },
            new PickerItem { Key = "SubItem3", IsSelected = false },
            new PickerItem { Key = "SubItem4", IsSelected = false }
        };

        ModelItemList = new()
        {
            new ModelClass { Title = "Item1", ItemList = RadioButtonList, SelectedItem = RadioButtonList[0] },
            new ModelClass { Title = "Item2", ItemList = RadioButtonList, SelectedItem = RadioButtonList[1] },
            new ModelClass { Title = "Item3", ItemList = RadioButtonList, SelectedItem = RadioButtonList[2] },
            new ModelClass { Title = "Item4", ItemList = RadioButtonList, SelectedItem = RadioButtonList[3] }
        };
    }
}

模型代码

ModelClass

public class ModelClass : ObservableObject
{
    private string _title = "";
    public string Title
    {
        get => _title;
        set => SetProperty(ref _title, value);
    }

    private List<PickerItem> _itemList = new();
    public List<PickerItem> ItemList
    {
        get => _itemList;
        set => SetProperty(ref _itemList, value);
    }
    
    private PickerItem _selectedItem;
    public PickerItem SelectedItem
    {
        get => _selectedItem;
        set => SetProperty(ref _selectedItem, value);
    }
}

PickerItem

public class PickerItem : ObservableObject
{
    private string _key = "";
    public string Key
    {
        get => _key;
        set => SetProperty(ref _key, value);
    }

    private bool _isSelected = false;
    public bool IsSelected
    {
        get => _isSelected;
        set => SetProperty(ref _isSelected, value);
    }
}

RadioButtonContentView XAML代码

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiSampleApp.Controls.RadioButtonContentView"
             x:Name="this">

    <VerticalStackLayout
        BindableLayout.ItemsSource="{Binding ItemSource, Source={x:Reference this}}"
        RadioButtonGroup.GroupName="{Binding GroupName, Source={x:Reference this}}">
        <BindableLayout.ItemTemplate>
            <DataTemplate>
                <Grid
                    ColumnSpacing="12"
                    ColumnDefinitions="20,*">

                    <RadioButton
                        Grid.Column="0"
                        IsChecked="{Binding IsSelected}"
                        Value="{Binding .}"
                        CheckedChanged="RadioButton_CheckedChanged"/>

                    <Label
                        Grid.Column="1"
                        Text="{Binding Key}"/>

                    <Grid.GestureRecognizers>
                        <TapGestureRecognizer
                            Tapped="TapGestureRecognizer_Tapped"
                            CommandParameter="{Binding .}"/>
                    </Grid.GestureRecognizers>
                </Grid>
            </DataTemplate>
        </BindableLayout.ItemTemplate>
    </VerticalStackLayout>
</ContentView>

RadioButtonContentView后端代码

public partial class RadioButtonContentView : ContentView
{
    public static readonly BindableProperty SelectedItemProperty = BindableProperty.Create(
        nameof(SelectedItem),
        typeof(PickerItem),
        typeof(RadioButtonContentView),
        null,
        propertyChanged: SelectedItemPropertyChanged);

    public PickerItem SelectedItem
    {
        get => (PickerItem)GetValue(SelectedItemProperty);
        set => SetValue(SelectedItemProperty, value);
    }

    private static void SelectedItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        if (oldValue is PickerItem oldPickerItem)
        {
            oldPickerItem.IsSelected = false;
        }

        if (newValue is PickerItem newPickerItem)
        {
            newPickerItem.IsSelected = true;
        }
    }

    public static readonly BindableProperty ItemSourceProperty = BindableProperty.Create(
        nameof(ItemSource),
        typeof(List<PickerItem>),
        typeof(RadioButtonContentView),
        new List<PickerItem>());

    public List<PickerItem> ItemSource
    {
        get => (List<PickerItem>)GetValue(ItemSourceProperty);
        set => SetValue(ItemSourceProperty, value);
    }

    public static readonly BindableProperty GroupNameProperty = BindableProperty.Create(
        nameof(GroupName),
        typeof(string),
        typeof(RadioButtonContentView),
        "");

    public string GroupName
    {
        get => (string)GetValue(GroupNameProperty);
        set => SetValue(GroupNameProperty, value);
    }

    public RadioButtonContentView()
    {
        InitializeComponent();
    }

    void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
    {
        if (sender is RadioButton radioButton)
        {
            if (e.Value)
            {
                var selectedPickerItem = radioButton.Value;
                SelectedItem = (PickerItem)selectedPickerItem;
            }
        }
    }

    void TapGestureRecognizer_Tapped(object sender, TappedEventArgs e)
    {
        if (e.Parameter is PickerItem selectedPickerItem)
        {
            SelectedItem = selectedPickerItem;
        }
    }
}

问题修复方案

1. 所有父项选中状态同步的问题

原因:ViewModel中所有ModelClass实例的ItemList都引用了同一个RadioButtonList集合,PickerItem是引用类型,修改任意一个父项中PickerItem的IsSelected属性,会影响所有引用该对象的父项。

修复:给每个ModelClass创建独立的PickerItem集合,避免复用同一列表。可以封装一个创建子列表的方法减少重复代码:

private List<PickerItem> CreatePickerItems(int defaultSelectedIndex)
{
    var items = new List<PickerItem>
    {
        new PickerItem { Key = "SubItem1", IsSelected = false },
        new PickerItem { Key = "SubItem2", IsSelected = false },
        new PickerItem { Key = "SubItem3", IsSelected = false },
        new PickerItem { Key = "SubItem4", IsSelected = false }
    };
    if(defaultSelectedIndex >=0 && defaultSelectedIndex < items.Count)
    {
        items[defaultSelectedIndex].IsSelected = true;
    }
    return items;
}

// 在ViewModel构造函数中调用
public RadioButtonViewModel()
{
    ModelItemList = new()
    {
        new ModelClass { Title = "Item1", ItemList = CreatePickerItems(0), SelectedItem = CreatePickerItems(0)[0] },
        new ModelClass { Title = "Item2", ItemList = CreatePickerItems(1), SelectedItem = CreatePickerItems(1)[1] },
        new ModelClass { Title = "Item3", ItemList = CreatePickerItems(2), SelectedItem = CreatePickerItems(2)[2] },
        new ModelClass { Title = "Item4", ItemList = CreatePickerItems(3), SelectedItem = CreatePickerItems(3)[3] }
    };
}

2. CheckedChanged事件多次触发的问题

原因:设置SelectedItem时会修改PickerItem.IsSelected,而该属性绑定到RadioButton的IsChecked,会再次触发CheckedChanged事件;同时组内未选中的RadioButton状态变化也会触发事件。

修复:添加状态标记避免循环触发,只处理选中状态的事件:

public partial class RadioButtonContentView : ContentView
{
    private bool _isUpdatingSelection = false;

    void RadioButton_CheckedChanged(object sender, CheckedChangedEventArgs e)
    {
        if (_isUpdatingSelection) return;
        if (sender is RadioButton radioButton && e.Value)
        {
            _isUpdatingSelection = true;
            SelectedItem = (PickerItem)radioButton.Value;
            _isUpdatingSelection = false;
        }
    }

    private static void SelectedItemPropertyChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = (RadioButtonContentView)bindable;
        if (view._isUpdatingSelection) return;
        
        view._isUpdatingSelection = true;
        if (oldValue is PickerItem oldPickerItem)
        {
            oldPickerItem.IsSelected = false;
        }

        if (newValue is PickerItem newPickerItem)
        {
            newPickerItem.IsSelected = true;
        }
        view._isUpdatingSelection = false;
    }

    // 其余代码不变
}

同时,需要将RadioButtonContentView的SelectedItem与父项ModelClass的SelectedItem双向绑定,在主页面XAML中修改:

<controls:RadioButtonContentView
    Grid.Row="1"
    GroupName="{Binding Title}"
    ItemSource="{Binding ItemList}"
    SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>

这样每个父项的选中状态会独立存储在自身的SelectedItem属性中,不会互相干扰。


内容的提问来源于stack exchange,提问作者Swati Chandra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 11:05:56