Search code examples
c#xamlmauivisualstatemanager

VisualStateManager in DataTemplate created by code


I have the following code:

MainPage.xml

<ContentPage x:Class="Test1.MainPage"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"  
    Title="MainPage">

    <ContentPage.Resources>
        <DataTemplate x:Key="DefaultItemTemplate">
            <Border Stroke="Transparent" BackgroundColor="WhiteSmoke" StrokeShape="RoundRectangle 4" 
                MinimumWidthRequest="36" MinimumHeightRequest="36">

                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup Name="CommonStates">
                        <VisualState Name="Normal" />
                        <VisualState Name="Selected">
                            <VisualState.Setters>
                                <Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
                                <Setter TargetName="Index" Property="Label.TextColor" Value="White" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>

                <Label x:Name="Index" Text="X" TextColor="Black" HorizontalOptions="Center" VerticalOptions="Center" />

            </Border>
        </DataTemplate>

    </ContentPage.Resources>

    <StackLayout Padding="16" Spacing="12">

        <StackLayout Orientation="Horizontal">
            <Label Text="1"/>
            <CheckBox x:Name="Check1" CheckedChanged="Check1_CheckedChanged"/>
            <ContentView x:Name="ContentView1"/>
        </StackLayout>

        <StackLayout Orientation="Horizontal">
            <Label Text="2"/>
            <CheckBox x:Name="Check2" CheckedChanged="Check2_CheckedChanged"/>
            <ContentView x:Name="ContentView2"/>
        </StackLayout>
                
        </StackLayout>
</ContentPage>

MainPage.xaml.cs

using Microsoft.Maui.Controls.Shapes;

namespace Test1;

public partial class MainPage : ContentPage {

    View view1;
    View view2;
    
    public MainPage() {
        InitializeComponent();

        //Taked from xaml resource
        view1 = (View)((DataTemplate)Resources["DefaultItemTemplate"]).CreateContent();
        ContentView1.Content = view1;
        
        //Created by code
        view2 = (View)DefaultItemTemplate.CreateContent();
        ContentView2.Content = view2;


    }


    private void Check1_CheckedChanged(object sender, CheckedChangedEventArgs e) {
        try {
            VisualStateManager.GoToState(view1, e.Value ? "Selected" : "Normal");

            var asd = view1.FindByName("Index");
            System.Diagnostics.Debug.WriteLine(asd ?? "null"); // works ok, it gets the label
        } catch (Exception ex) { 
            System.Diagnostics.Debug.WriteLine($"Error: {ex}"); 
        }
    }

    private void Check2_CheckedChanged(object sender, CheckedChangedEventArgs e) {
        try {
            VisualStateManager.GoToState(view2, e.Value ? "Selected" : "Normal");

            var asd = view2.FindByName("Index");
            System.Diagnostics.Debug.WriteLine(asd ?? "null"); // returns null, label not found??
        } catch (Exception ex) { 
            System.Diagnostics.Debug.WriteLine($"Error: {ex}"); 
        }
    }

    private static DataTemplate DefaultItemTemplate => new DataTemplate( () => {
        var view = new Border() {
            Stroke = Colors.Transparent,
            BackgroundColor = Colors.WhiteSmoke,
            StrokeShape = new RoundRectangle { CornerRadius = 4 },
            MinimumWidthRequest = 36,
            MinimumHeightRequest = 36,
        };

        var label = new Label() {
            StyleId = "Index", // it doenst work
            Text = "X",
            TextColor = Colors.Black,
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center
        };
        view.Content = label;

        var commonStateGoup = new VisualStateGroup { Name = "CommonStates" };
        var stateNormal = new VisualState { Name = "Normal" };
        var stateSelected = new VisualState { Name = "Selected" };

        stateSelected.Setters.Add(new Setter {
            Property = BackgroundColorProperty,
            Value = Application.Current!.Resources.TryGetValue("Primary",out var primaryColor) ? (Color)primaryColor : Colors.BlueViolet,
        });
        // Commenting this works, but label is not affected. Else 
        /*
        stateSelected.Setters.Add(new Setter {
            TargetName =  "Index", // doesnt work
            Property = Label.TextColorProperty,
            Value = Colors.White,
        });
        */
        commonStateGoup.States.Add(stateNormal);
        commonStateGoup.States.Add(stateSelected);

        VisualStateManager.GetVisualStateGroups(view).Add(commonStateGoup);

        return view;
    });
}

what im trying to achieve is to create the DefaultItemTemplate from the xaml resource but by code, and currently i cant make the VisualStateManager target the Label. It works as expected if i take the template from the xaml page resource, but not from de code behind.

How can i make it work?


Solution

  • Thanks to the link posted in comments by Liqun Shen-MSFT, i could get it work. Here is the DataTemplate code:

    private static DataTemplate DefaultItemTemplate => new( () => {
        var view = new Border() {
            Stroke = Colors.Transparent,
            StrokeThickness = 4d,
            BackgroundColor = Colors.WhiteSmoke,
            StrokeShape = new RoundRectangle { CornerRadius = 4 },
            MinimumWidthRequest = 36,
            MinimumHeightRequest = 36,
        };
        var label = new Label() {
            Text = "-",
            TextColor = Colors.Black,
            FontSize = 14,
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center
        };
        view.Content = label;
    
        // using Microsoft.Maui.Controls.Internals;
        // this make the magic, you need to set the namescope and register the name this way
        INameScope nameScope = new NameScope();
        NameScope.SetNameScope(view, nameScope);
        //nameScope.RegisterName("Root", view);
        nameScope.RegisterName("Index", label);
    
    
        var commonStateGoup = new VisualStateGroup { Name = "CommonStates" };
        var stateNormal = new VisualState { Name = "Normal" };
        var stateSelected = new VisualState { Name = "Selected" };
    
        stateNormal.Setters.Add(new());
    
        stateSelected.Setters.Add(new Setter {
            //TargetName = "Root",
            Property = BackgroundColorProperty,
            Value = Application.Current!.Resources.TryGetValue("Primary",out var primaryColor) ? (Color)primaryColor : Colors.BlueViolet,
        });
        stateSelected.Setters.Add(new Setter {
            TargetName =  "Index",
            Property = Label.TextColorProperty,
            Value = Colors.White,
        });
    
        commonStateGoup.States.Add(stateNormal);
        commonStateGoup.States.Add(stateSelected);
    
        VisualStateManager.GetVisualStateGroups(view).Add(commonStateGoup);
    
        return view;
    });