unoplatform / uno.extensions

Libraries to ease common developer tasks associated with building multi-platform mobile, desktop and web applications using Uno Platform or WinAppSDK.
https://platform.uno/
Other
73 stars 47 forks source link

[C# Markup] VisualStateManager not working in Custom control (or I am missing something...) #2565

Open kucint opened 3 weeks ago

kucint commented 3 weeks ago

Current behavior

I implemented VisualStateManager as presented in example: VisualStateManager In this example, VisualStateManager is a property of a topmost UIElement. It is working fine.

I created couples of other scenarios where VisualStateManager is not working, don't know why.

Have look on different scenarios bellow:

    /// <summary>
    /// VisualStateManager attached to the topmost Grid
    /// working as expected
    /// </summary>
    private Grid Content1() => new Grid()
        .HorizontalAlignment(HorizontalAlignment.Center)
        .VerticalAlignment(VerticalAlignment.Center)
        .RowDefinitions(
            new RowDefinition().Height(GridLength.Auto),
            new RowDefinition().Height(GridLength.Auto),
            new RowDefinition().Height(GridLength.Auto))
        .Children(
            new TextBlock()
            .Grid(g => g.Row(0))
            .Text("Hello Uno Platform!")
            .Name(out var textBlock),
            new Button()
            .Grid(g => g.Row(1))
            .Content("A")
            .Margin(5)
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateA, true))),
            new Button()
            .Grid(g => g.Row(2))
            .Margin(5)
            .Content("B")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateB, true)))
        )
        .VisualStateManager(vsm => vsm.Group(group => group
            .State(Common.StateA, state => state
                .Setters(grid => grid.Background(Colors.AliceBlue))
                .Setters(textBlock, tb => tb.Text(Common.StateA)))
            .State(Common.StateB, state => state
                .Setters(grid => grid.Background(Colors.Orange))
                .Setters(textBlock, tb => tb.Text(Common.StateB)))));

    /// <summary>
    /// VisualStateManager attached to the child: TextBlock
    /// not working as expected
    /// </summary>
    private StackPanel Content2() => new StackPanel()
        .VerticalAlignment(VerticalAlignment.Center)
        .HorizontalAlignment(HorizontalAlignment.Center)
        .Children(
            new TextBlock()
            .Text("Hello Uno Platform!")
            .VisualStateManager(vsm => vsm.Group(group => group
                .State(Common.StateA, state => state
                    .Setters(ctrl => ctrl.Text(Common.StateA))
                    .Setters(ctrl => ctrl.Foreground(Colors.DarkBlue))
                )
                .State(Common.StateB, state => state
                    .Setters(ctrl => ctrl.Text(Common.StateB))
                    .Setters(ctrl => ctrl.Foreground(Colors.DarkRed))
                ))),
            new Button()
            .Content("A")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateA, true))),
            new Button()
            .Content("B")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateB, true))));

    /// <summary>
    /// VisualStateManager attached to the topmost StackPanel
    /// working as expected
    /// </summary>
    private StackPanel Content3() => new StackPanel()
        .VerticalAlignment(VerticalAlignment.Center)
        .HorizontalAlignment(HorizontalAlignment.Center)
        .Children(
            new TextBlock()
            .Name(out var textBlock)
            .Text("Hello Uno Platform!"),
            new Button()
            .Content("A")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateA, true))),
            new Button()
            .Content("B")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateB, true)))
        )
        .VisualStateManager(vsm => vsm.Group(group => group
                .State(Common.StateA, state => state
                    .Setters(textBlock, ctrl => ctrl.Text(Common.StateA))
                    .Setters(textBlock, ctrl => ctrl.Foreground(Colors.DarkBlue))
                )
                .State(Common.StateB, state => state
                    .Setters(textBlock, ctrl => ctrl.Text(Common.StateB))
                    .Setters(textBlock, ctrl => ctrl.Foreground(Colors.DarkRed))
            )));

    /// <summary>
    /// VisualStateManager attached to the panel's child: TextBlock in Custom Control
    /// not working as expected
    /// </summary>
    private MyControl4 Content4() => new MyControl4();

    /// <summary>
    /// VisualStateManager attached to the topmost StackPanel in Custom Control
    /// not working as expected
    /// </summary>
    private MyControl5 Content5() => new MyControl5();

/// <summary>
/// VisualStateManager attached to the TextBlock
/// not working as expected
/// </summary>
internal class MyControl4 : Control
{
    public MyControl4()
    {
        DefaultStyleKey = typeof(MyControl4);

        this
        .Style(new Style<MyControl4>()
            .Setters(s => s.Template(ctrl => CreateTemplate(ctrl))));
    }

    private UIElement CreateTemplate(MyControl4 ctrl) => new StackPanel()
        .VerticalAlignment(VerticalAlignment.Center)
        .HorizontalAlignment(HorizontalAlignment.Center)
        .Children(
            new TextBlock()
            .Text("Hello Uno Platform!")
            .VisualStateManager(vsm => vsm.Group(group => group
                .State(Common.StateA, state => state
                    .Setters(ctrl => ctrl.Text(Common.StateA))
                    .Setters(ctrl => ctrl.Foreground(Colors.DarkBlue))
                )
                .State(Common.StateB, state => state
                    .Setters(ctrl => ctrl.Text(Common.StateB))
                    .Setters(ctrl => ctrl.Foreground(Colors.DarkRed))
                )))
            ,
            new Button()
            .Content("A")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateA, true))),
            new Button()
            .Content("B")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateB, true)))
        );
}

/// <summary>
/// VisualStateManager attached to the StackPanel
/// not working as expected
/// </summary>
internal class MyControl5 : Control
{
    public MyControl5()
    {
        DefaultStyleKey = typeof(MyControl5);

        this
        .Style(new Style<MyControl5>()
            .Setters(s => s.Template(ctrl => CreateTemplate(ctrl))));
    }

    private UIElement CreateTemplate(MyControl5 ctrl) => new StackPanel()
        .VerticalAlignment(VerticalAlignment.Center)
        .HorizontalAlignment(HorizontalAlignment.Center)
        .Children(
            new TextBlock()
            .Name(out var textBlock)
            .Text("Hello Uno Platform!"),
            new Button()
            .Content("A")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateA, true))),
            new Button()
            .Content("B")
            .Name(c => c.Click += ((s, e) => VisualStateManager.GoToState(this, Common.StateB, true)))

        .VisualStateManager(vsm => vsm.Group(group => group
            .State(Common.StateA, state => state
                .Setters(textBlock, ctrl => ctrl.Text(Common.StateA))
                .Setters(textBlock, ctrl => ctrl.Foreground(Colors.DarkBlue))
            )
            .State(Common.StateB, state => state
                .Setters(textBlock, ctrl => ctrl.Text(Common.StateB))
                .Setters(textBlock, ctrl => ctrl.Foreground(Colors.DarkRed))
            )))
        );
}

Expected behavior

VisualStateManager should work in all scenarios, I guess, or I miss something...

How to reproduce it (as minimally and precisely as possible)

MINIMAL REPRO PROJECT: VisualStateManagerApp.zip

STEPS TO REPRODUCE

    public MainPage() => this
        .Background(ThemeResource.Get<Brush>("ApplicationPageBackgroundThemeBrush"))
        .Content(Content1()); // modify here

Environment

Nuget Package (s):

Package Version(s):

"Uno.Sdk": "5.3.108"

Issue is observed on both frameworks Desktop and WindowsSdk

Affected platform(s):

Visual Studio:

Relevant plugins:

Anything else we need to know?

MartinZikmund commented 2 weeks ago

The problem here is that the VisualStateManager should only be set on the direct child of a templated control, as documentation says:

Conceptually, VisualStateManager.VisualStateGroups contains the visual states for a control, as an immediate child tag of the template root in a control template.

And this doc page says:

When you use StateTriggers, ensure that the VisualStateGroup is declared under the first child of the root in order for the triggers to take effect automatically.

This limitation is based on WinUI and we cannot easily circumvent it. However, you can create custom controls that are nested and that will have their own VisualStateManager on the root child

kucint commented 2 weeks ago

I got it: VisualStateManager must be set on the direct child of a templated control.

But this is a case in "MyControl5" and the VisualStateManager is lot working anyway.... @MartinZikmund , could you please reopen issue. I can't do it...

MartinZikmund commented 2 weeks ago

You are right, missed that one. It seems that it may be some C# markup issue.