Chuck
Chuck

Reputation: 87

MAUI RadioButton with ControlTemplate and DataTemplate doesn't render content

I am trying to create RadioButtons that bind to a list of content using a DataTemplate, and render based on a ControlTemplate. The data binding is working - I get a button for each element in the list. The VisualStateManager is also working - I get the requested state changes as I click on the buttons.

However, the ContentPresenter is not working - my buttons are empty. I'm also getting an InvalidCastException each time I click on a button. Here is my code.

<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>

I'm also puzzled because the Visual Tree shows the expected control hierarchy. enter image description here

Oh, and BTW - my RadioButtonGroup.SelectedValue is not working either.

I would appreciate any assistance. Thanks.

Chuck

Upvotes: 0

Views: 1674

Answers (2)

Ben
Ben

Reputation: 2995

With Content part of ControlTemplate and use of 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 class and 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" }
    };
  }
}

Upvotes: 0

Jianwei Sun - MSFT
Jianwei Sun - MSFT

Reputation: 4332

RadioButton with ControlTemplate and DataTemplate doesn't render content.

You can add RadioButton to a 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>

CustomControls.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());
    }
}

And then use it in 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>

Page.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"
        });
    }
}

The ContentPresenter works well. I don't know code structure of ActivitiesViewModel, so replace it with Monkey.

Upvotes: 0

Related Questions