Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply styles to all inner elements in MAUI on state change

Tags:

xaml

maui

This is basically simple UI that would conditionally render a label with text Triggered - plain in case the ShowContent property of binding is True. (Overly simplified example here but it works and I can see the label toggles).

<Grid>
   <Button Click="ChangeState"/>
   <ContentView>
      <ContentView.Style>
            <Style TargetType="ContentView">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ShowContent}" Value="True" TargetType="ContentView">
                        <Setter Property="ControlTemplate">
                            <Setter.Value>
                                <ControlTemplate>
                                    <Label>Triggered - Plain</Label>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentView.Style>
   </ContentView>
</Grid>

There is also a button that would toggle the state of the Grid:

public void ChangeState(object sender, EventArgs e){
    this.state = !this.state; // toggle
    if(this.state){
      VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Selected);
    } else {
      VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
    }
}

Problem

I am not sure how to apply a different style to the label based on selected state.

If you use VisualStateManager, you need to have a name on the Label. I want to apply selected style in a blanket way on all inner labels.

Also, if we target the label with a name, when the Label is not on the UI (due to state ShowContent being False), GoToState fails with null exception as it cannot find the label.

The best solution seems to be using CSS but that does not support defined colors and dynamic resources (AFAIK).

Any idea what to do?

Update: one possible solution is to apply the state change to all inner elements:

private IList<T> FindAllChildren<T>()
    where T : IVisualTreeElement
{
    return this.GetVisualTreeDescendants()
        .Where(e => e is T)
        .Cast<T>()
        .ToList();
}

private void ApplyState(string state)
{
    VisualStateManager.GoToState(this, state);
    FindAllChildren<VisualElement>().ForEach(e => VisualStateManager.GoToState(e, state));
}

public void ChangeState(object sender, EventArgs e){
    this.state = !this.state; // toggle
    if(this.state){
      ApplyState(VisualStateManager.CommonStates.Selected);
    } else {
      ApplyState(VisualStateManager.CommonStates.Normal);
    }
}

You still need to create VisualStateGroup styling for the labels and give labels a specific style/class:

<label class="Selectable">...</label>
<Style class="Selectable" TargetType="Label">
   <Setter Property="VisualStateManager.VisualStateGroups">
      <VisualStateGroupList>
          <VisualStateGroup Name="all">
               <VisualState Name="Normal">
                   <VisualState.Setters>
                       <Setter Property="Label.TextColor" Value="{DynamicResource Normal_Color}"/>
                   </VisualState.Setters>
               </VisualState>
               <VisualState Name="Selected">
                  <VisualState.Setters>
                      <Setter Property="Label.TextColor" Value="{StaticResource Selected_Color}"/>
                  </VisualState.Setters>
                </VisualState>
          </VisualStateGroup>
      </VisualStateGroupList>
   </Setter>
</Style>
like image 918
Bakhshi Avatar asked Oct 24 '25 08:10

Bakhshi


1 Answers

I recommend that you work around this. Too many bugs, and too different behavior on the different platforms for containers.

Fixing the visual state is one thing. Then you need to fix "IsEnabled" for child problem. After that if you change the visibility, you will notice that on IOS it is doing one thing, on android - another. (You will start losing this visual state from time to time). At some point you will start looking for ways to force the page to redraw itself.

My advice is, for now, give up on this idea. Until those problems are solved. Wasted too many hours trying to make this work for all platforms.

(Some of the issues are 6+ months old, and they keep pushing them to backlog.)

This is me, asking the same thing, a month ago: Pass the VisualState of CollectionView Item VisualElement to its child VisualElements

Edit: So, what work arounds I use. Besides styles, visual states, data triggers?

ControlTemplates and Messages between ViewModel <-> View.

Control templates are reusable pieces of user interface, and there isn't much you have to do. You can make all VisualElements bind to the same thing, using TemplatedParent as BindingContext of the container.

Messages I use for some sorts of animations (And other special requests). You can in the ViewModel generate a message, that will be handled (or not) by the View. You have very good control over your View, but you do not break MVVM by coupling them.

A Warning: Every work around is parasitic code (you do something the wrong way, because someone else has been doing his job the wrong way). That code sooner or later will have to be deleted/replaced. Mark it with TODO, because it may take huge part of your app, and later it will be hard to find out all usage places. For now test on IOS. It takes much less work to make it work on IOS, then fix Android, than the other way around.

like image 136
H.A.H. Avatar answered Oct 26 '25 03:10

H.A.H.