MAUI RadioButton с ControlTemplate и DataTemplate не отображает содержимое

Я пытаюсь создать RadioButtons, которые привязываются к списку контента с помощью DataTemplate и отображают на основе ControlTemplate. Привязка данных работает - я получаю кнопку для каждого элемента в списке. VisualStateManager также работает - я получаю запрошенные изменения состояния, когда нажимаю кнопки.

Однако ContentPresenter не работает - мои кнопки пусты. Я также получаю InvalidCastException каждый раз, когда я нажимаю на кнопку. Вот мой код.

<ContentPage.BindingContext>
    <pages:ActivitiesViewModel/>
</ContentPage.BindingContext>

<ContentPage.Resources>
    <ControlTemplate x:Key = "MuscleGroupButtonsTemplate">
        <Border
        Stroke = "{StaticResource SecondaryBrush}"
        StrokeThickness = "1"
        Background = "Transparent"
        >
            <Border.StrokeShape>
                <RoundRectangle CornerRadius = "8"/>
            </Border.StrokeShape>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroupList>
                    <VisualStateGroup x:Name = "CheckedStates">
                        <VisualState x:Name = "Checked">
                            <VisualState.Setters>
                                <Setter
                                    Property = "Background"
                                    Value = "{StaticResource AccentBrush}"/>
                                <Setter
                                    Property = "Stroke"
                                    Value = "{StaticResource SecondaryBrush}"/>
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name = "Unchecked">
                            <VisualState.Setters>
                                <Setter
                                    Property = "Background"
                                    Value = "{StaticResource NeutralBrush}"/>
                                <Setter
                                    Property = "Stroke"
                                    Value = "{StaticResource SecondaryBrush}"/>
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateGroupList>
            </VisualStateManager.VisualStateGroups>
            <ContentPresenter HeightRequest = "64"/>
        </Border>
    </ControlTemplate>
</ContentPage.Resources>

<VerticalStackLayout Spacing = "12" 
    RadioButtonGroup.GroupName = "MuscleGroups"
    RadioButtonGroup.SelectedValue = "{Binding SelectedMuscleGroup}"
    BindableLayout.ItemsSource = "{Binding MuscleGroupList}">

    <VerticalStackLayout.Resources>
        <Style TargetType = "RadioButton">
            <Setter Property = "ControlTemplate" Value = "{StaticResource MuscleGroupButtonsTemplate}"/>
        </Style>
    </VerticalStackLayout.Resources>

    <BindableLayout.ItemTemplate>
        <DataTemplate x:DataType = "m:MuscleGroup">
            <RadioButton Value = "{Binding Id}" CheckedChanged = "OnMuscleGroupChanged">
                <RadioButton.Content>
                    <HorizontalStackLayout Margin = "12,6,0,0" Spacing = "8">
                        <Image WidthRequest = "64"
                            Aspect = "AspectFit"
                            Source = "{Binding Icon}"/>
                        <Label Style = "{StaticResource Headline}" VerticalOptions = "Center">
                            <Label.FormattedText>
                                <FormattedString>
                                    <Span Text = "{Binding Name}"/>
                                </FormattedString>
                            </Label.FormattedText>
                        </Label>
                    </HorizontalStackLayout>
                </RadioButton.Content>
            </RadioButton>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</VerticalStackLayout>

Я также озадачен тем, что визуальное дерево показывает ожидаемую иерархию элементов управления.

О, и, кстати, мой RadioButtonGroup.SelectedValue тоже не работает.

Буду признателен за любую помощь. Спасибо.

Чак

Что отображается на экране, если вы удалите использование шаблона управления?

ToolmakerSteve 07.02.2023 20:59

Вертикальный список RadioButtons по умолчанию - очерченные круги с заполненным выбранным. Но если я удалю оболочку <RadioButton></RadioButton>, я получу желаемое содержимое - без кнопок, конечно.

Chuck 07.02.2023 21:59
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
80
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

RadioButton с ControlTemplate и DataTemplate не отображает содержимое.

Вы можете добавить RadioButton в CustomControl:

<ContentView xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
       xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml" 
       x:Name = "this" 
       x:Class = "MauiApp1.CustomControls.CustomControls">
       <ContentView.Resources>
              <ControlTemplate x:Key = "MuscleGroupButtonsTemplate">
                     <Border Stroke = "{StaticResource SecondaryBrush}" StrokeThickness = "2" Background = "Transparent">
                            <Border.StrokeShape>
                                   <RoundRectangle CornerRadius = "8"/>
                            </Border.StrokeShape>
                            <VisualStateManager.VisualStateGroups>
                                   <VisualStateGroupList>
                                          <VisualStateGroup x:Name = "CheckedStates">
                                                 <VisualState x:Name = "Checked">
                                                        <VisualState.Setters>
                                                               <Setter Property = "Background" Value = "LightSkyBlue"/>
                                                               <Setter Property = "Stroke" Value = "{StaticResource SecondaryBrush}"/>
                                                        </VisualState.Setters>
                                                 </VisualState>
                                                 <VisualState x:Name = "Unchecked">
                                                        <VisualState.Setters>
                                                               <Setter Property = "Background" Value = "YellowGreen"/>
                                                               <Setter Property = "Stroke" Value = "{StaticResource SecondaryBrush}"/>
                                                        </VisualState.Setters>
                                                 </VisualState>
                                          </VisualStateGroup>
                                   </VisualStateGroupList>
                            </VisualStateManager.VisualStateGroups>
                            <ContentPresenter HeightRequest = "50" />
                     </Border>
              </ControlTemplate>
              <Style TargetType = "RadioButton">
                     <Setter Property = "ControlTemplate" Value = "{StaticResource MuscleGroupButtonsTemplate}" />
              </Style>
       </ContentView.Resources>
       <VerticalStackLayout BindingContext = "{x:Reference this}">
              <RadioButton Content = "{Binding CardTitle}" 
                           GroupName = "MuscleGroups" 
                           CheckedChanged = "OnMuscleGroupChanged"/>
       </VerticalStackLayout>
</ContentView>

Пользовательские элементы управления.xaml.cs:

public partial class CustomControls : ContentView
{
    public static readonly BindableProperty CardTitleProperty
        = BindableProperty.Create(nameof(CardTitle), typeof(string),
                                   typeof(CustomControls), string.Empty);
    public string CardTitle
    {
        get => (string)GetValue(CustomControls.CardTitleProperty);
        set => SetValue(CustomControls.CardTitleProperty, value);
    }
    public CustomControls()
    {
        InitializeComponent();

    }
    private void OnMuscleGroupChanged(object sender, CheckedChangedEventArgs e)
    {
        RadioButton radioButton = (RadioButton)sender;
        Console.WriteLine(radioButton.Content.ToString().Trim());
    }
}

А затем используйте его в Page.xaml:

<ContentPage xmlns = "http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x = "http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:controls = "clr-namespace:MauiApp1.CustomControls" 
             x:Class = "MauiApp1.NewPage2" 
             Title = "NewPage2">
       <VerticalStackLayout Spacing = "12">
              <CollectionView x:Name = "listview">
                     <CollectionView.ItemTemplate>
                            <DataTemplate>
                                   <controls:CustomControls CardTitle = "{Binding Name}"/>
                            </DataTemplate>
                     </CollectionView.ItemTemplate>
              </CollectionView>
       </VerticalStackLayout>
</ContentPage>

Страница.xaml.cs:

public partial class NewPage2 : ContentPage
{
    private List<Monkey> source = new List<Monkey>();
    public NewPage2()
    {
        InitializeComponent();
        CreateMonkeyCollection();
        listview.ItemsSource = source;
    }

    void CreateMonkeyCollection()
    {
        source.Add(new Monkey
        {
            Name = "Golden Lion Tamarin",
            Location = "Brazil",
            Details = "The golden lion tamarin also known as the golden marmoset, is a small New World monkey of the family Callitrichidae.",
            ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/Golden_lion_tamarin_portrait3.jpg/220px-Golden_lion_tamarin_portrait3.jpg"
        });
        source.Add(new Monkey
        {
            Name = "Mandrill",
            Location = "Southern Cameroon, Gabon, and Congo",
            Details = "The mandrill is a primate of the Old World monkey family, closely related to the baboons and even more closely to the drill. It is found in southern Cameroon, Gabon, Equatorial Guinea, and Congo.",
            ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Mandrill_at_san_francisco_zoo.jpg/220px-Mandrill_at_san_francisco_zoo.jpg"
        });
        source.Add(new Monkey
        {
            Name = "Proboscis Monkey",
            Location = "Borneo",
            Details= "The proboscis monkey or long-nosed monkey, known as the bekantan in Malay, is a reddish-brown arboreal Old World monkey that is endemic to the south-east Asian island of Borneo.",
            ImageUrl= "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Proboscis_Monkey_in_Borneo.jpg/250px-Proboscis_Monkey_in_Borneo.jpg"
        });
        source.Add(new Monkey
        {
            Name = "Golden Snub-nosed Monkey",
            Location = "China",
            Details= "The golden snub-nosed monkey is an Old World monkey in the Colobinae subfamily. It is endemic to a small area in temperate, mountainous forests of central and Southwest China. They inhabit these mountainous forests of Southwestern China at elevations of 1,500-3,400 m above sea level. The Chinese name is Sichuan golden hair monkey. It is also widely referred to as the Sichuan snub-nosed monkey. Of the three species of snub-nosed monkeys in China, the golden snub-nosed monkey is the most widely distributed throughout China.",
            ImageUrl= "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Golden_Snub-nosed_Monkeys%2C_Qinling_Mountains_-_China.jpg/165px-Golden_Snub-nosed_Monkeys%2C_Qinling_Mountains_-_China.jpg"
        });
    }
}

ContentPresenter работает хорошо. Я не знаю структуру кода ActivitiesViewModel, поэтому замените его на Monkey.

Спасибо за предложение. Но вы сказали: «Вы МОЖЕТЕ добавить RadioButton в CustomControl», а не «Вы ДОЛЖНЫ добавить RadioButton в CustomControl». Итак, следующий комментарий выглядел намного проще, и он работал.

Chuck 08.02.2023 18:55
Ответ принят как подходящий

С Content частью ControlTemplate и использованием TemplateBinding Content.<property>:

<ControlTemplate x:Key = "RadioButtonControlTemplate">
  <Border>
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroupList>
        <VisualStateGroup x:Name = "CheckedStates">
          <VisualState x:Name = "Checked">
            <VisualState.Setters>
              <Setter Property = "Stroke" Value = "Red"/>
            </VisualState.Setters>
          </VisualState>
          <VisualState x:Name = "Unchecked">
            <VisualState.Setters>
              <Setter Property = "Stroke" Value = "Green"/>
            </VisualState.Setters>
          </VisualState>
        </VisualStateGroup>
      </VisualStateGroupList>
    </VisualStateManager.VisualStateGroups>

    <VerticalStackLayout x:DataType = "m:NameId">
      <Label Text = "{TemplateBinding Content.Name}"/>
      <Label Text = "{TemplateBinding Content.Id}"/>
    </VerticalStackLayout>

  </Border>
</ControlTemplate>

VerticalStackLayout:

<VerticalStackLayout
  RadioButtonGroup.GroupName = "NameIdGroup"
  BindableLayout.ItemsSource = "{Binding NameIdList}">

  <VerticalStackLayout.Resources>
    <Style TargetType = "RadioButton">
      <Setter Property = "ControlTemplate" 
              Value = "{StaticResource RadioButtonControlTemplate}"/>
    </Style>
  </VerticalStackLayout.Resources>

  <BindableLayout.ItemTemplate>
    <DataTemplate>
      <RadioButton Content = "{Binding .}">
        <RadioButton.GestureRecognizers>
          <TapGestureRecognizer 
            Tapped = "RadioButton_Tapped" 
            CommandParameter = "{Binding .}" />
        </RadioButton.GestureRecognizers>
      </RadioButton>
    </DataTemplate>
  </BindableLayout.ItemTemplate>

</VerticalStackLayout>

NameId класс и ViewModel:

public class NameId
{
  public string Name { get; set; }
  public string Id { get; set; }
}

public class ViewModel
{
  public ObservableCollection<NameId> NameIdList { get; set; }

  public ViewModel()
  {
    NameIdList = new ObservableCollection<NameId>
    {
        new NameId { Id = "Id A", Name = "Name A" },
        new NameId { Id = "Id B", Name = "Name B" },
        new NameId { Id = "Id C", Name = "Name C" }
    };
  }
}

Другие вопросы по теме