Search code examples
xamarin.formsdatatriggerexpanderindicator

Xamarin.Forms: how to apply a style on a Expander containing an Indicator


I would like to re use some Expanders in a Xamarin.Forms app.

The Expander uses Image as FontImageSource to display chevrons as Indicator, and the DataTrigger on the Image allows to switch between up/down chevron:

<Expander>
    <Expander.Header>
        <Grid>
            <Label Text="Who we are?"
                    Style="{StaticResource AboutPageCategoryLabelStyle}" />
            <Image
                    HeightRequest="20"
                    HorizontalOptions="End"
                    VerticalOptions="Center"
                    WidthRequest="20">
                    <Image.Source>
                        <FontImageSource
                            FontFamily="FontAwesomeLight"
                            Glyph="{StaticResource FalIconChevronDown}"
                            Size="20"
                            Color="Gray" />
                    </Image.Source>
                    <Image.Triggers>
                        <DataTrigger
                            Binding="{Binding Source={RelativeSource AncestorType={x:Type Expander}}, Path=IsExpanded}"
                            TargetType="Image"
                            Value="True">
                    <Setter Property="Source">
                        <Setter.Value>
                            <FontImageSource
                                FontFamily="FontAwesomeLight"
                                Glyph="{StaticResource FalIconChevronUp}"
                                Size="20"
                                Color="Gray" />
                        </Setter.Value>
                    </Setter>
                        </DataTrigger>
                    </Image.Triggers>
            </Image>
            </Grid>
    </Expander.Header>
    <Expander.ContentTemplate>
        <DataTemplate>
            ...
        </DataTemplate>
    </Expander.ContentTemplate>
</Expander>

I've tried to create a Style to duplicate easily the same Expander, but it doesn't work:

I can define the style of the Image for the "default" indicator mode:

<Style x:Key="ChevronImageForExpander" TargetType="Image">
    <Setter Property="BackgroundColor">Transparent</Setter>
    <Setter Property="HorizontalOptions">End</Setter>
    <Setter Property="VerticalOptions">Center</Setter>
    <Setter Property="HeightRequest">20</Setter>
    <Setter Property="WidthRequest">20</Setter>
    <Setter Property="Source">
        <Setter.Value>
            <FontImageSource Glyph="{StaticResource FalIconChevronDown}"
                                FontFamily="FontAwesomeLight"
                                Size="20"
                                Color="{StaticResource Gray-600}"/>
        </Setter.Value>
    </Setter>
</Style>

Then in my View I apply this style:

<Expander>
    <Expander.Header>
        <Grid>
            <Label Text="Header" />
            <Image Style="{StaticResource ChevronImageForExpander}">
            </Image>
        </Grid>
    </Expander.Header>
    <Expander.ContentTemplate>
        <DataTemplate>
            <Grid>
                <Label Text="Cotnent" />
            </Grid>
        </DataTemplate>
    </Expander.ContentTemplate>
</Expander>

This works fine, but I only have the "default" indicator image.

I've tried to define the style through a Trigger for the "expanded" indicator mode:

<Style x:Key="ChevronImageForExpanderTrigger" TargetType="Image">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Source={RelativeSource AncestorType={x:Type Expander}}, Path=IsExpanded}"
                        TargetType="Image"
                        Value="True">
            <Setter Property="Source">
                <Setter.Value>
                    <FontImageSource
                        FontFamily="FontAwesomeLight"
                        Glyph="{StaticResource FalIconChevronUp}"
                        Size="20"
                        Color="{StaticResource Gray-600}" />
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>
</Style>

This also works fine, but I only have the "expanded" indicator image.

So I've tried to "merge" the 2 styles like this:

<Style x:Key="ChevronImageForExpander" TargetType="Image">
    <Setter Property="BackgroundColor">Transparent</Setter>
    <Setter Property="HorizontalOptions">End</Setter>
    <Setter Property="VerticalOptions">Center</Setter>
    <Setter Property="HeightRequest">20</Setter>
    <Setter Property="WidthRequest">20</Setter>
    <Setter Property="Source">
        <Setter.Value>
            <FontImageSource Glyph="{StaticResource FalIconChevronDown}"
                                FontFamily="FontAwesomeLight"
                                Size="20"
                                Color="{StaticResource Gray-600}"/>
        </Setter.Value>
    </Setter>
    <Setter Property="Triggers">
        <Setter.Value>
            <DataTrigger Binding="{Binding Source={RelativeSource AncestorType={x:Type Expander}}, Path=IsExpanded}"
                            TargetType="Image"
                            Value="True">
                <Setter Property="Source">
                    <Setter.Value>
                        <FontImageSource
                            FontFamily="FontAwesomeLight"
                            Glyph="{StaticResource FalIconChevronUp}"
                            Size="20"
                            Color="{StaticResource Gray-600}" />
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Setter.Value>
    </Setter>
</Style>

But this doesn't work and I get an Exception: "System.InvalidOperationException: The BindableProperty "Triggers" is readonly.".

I could also create a control, but I would like to use different kinds of content for the Expander.ContentTemplate: labels, images, ...

How should be the better approach to easily reuse this Expander?

Edit: add some code for the Style


Solution

  • Finally it was just a problem of Trigger declaration in the "global" style: we must use <Style.Triggers> instead of <Setter Property="Triggers">

    So, like this it works well:

    <Style x:Key="ChevronImageForExpander" TargetType="Image">
        <Setter Property="BackgroundColor">Transparent</Setter>
        <Setter Property="HorizontalOptions">End</Setter>
        <Setter Property="VerticalOptions">Center</Setter>
        <Setter Property="HeightRequest">20</Setter>
        <Setter Property="WidthRequest">20</Setter>
        <Setter Property="Source">
            <Setter.Value>
                <FontImageSource Glyph="{StaticResource FalIconChevronDown}"
                                    FontFamily="FontAwesomeLight"
                                    Size="20"
                                    Color="{StaticResource Gray-600}"/>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <DataTrigger Binding="{Binding Source={RelativeSource AncestorType={x:Type Expander}}, Path=IsExpanded}"
                            TargetType="Image"
                            Value="True">
                <Setter Property="Source">
                    <Setter.Value>
                        <FontImageSource
                            FontFamily="FontAwesomeLight"
                            Glyph="{StaticResource FalIconChevronUp}"
                            Size="20"
                            Color="{StaticResource Gray-600}" />
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    And in the XAML:

    <Expander>
        <Expander.Header>
            <Grid>
                <Label Text="Header" />
                <Image Style="{StaticResource ChevronImageForExpander}">
                </Image>
            </Grid>
        </Expander.Header>
        <Expander.ContentTemplate>
            <DataTemplate>
                <Grid>
                    <Label Text="Content" />
                </Grid>
            </DataTemplate>
        </Expander.ContentTemplate>
    </Expander>
    

    Maybe is there another approach to apply ContentTemplate to the Header (both to the Label and to the Image), but at least, it's correct like this.