Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Add deferral to VisualState Setters for x:Load="False" #801

Open
JulianSelman opened this issue Jun 4, 2019 · 2 comments
Open
Labels
area-Styling feature proposal New feature proposal needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) team-Controls Issue for the Controls team

Comments

@JulianSelman
Copy link

Proposal: Add deferral to VisualState Setters for x:Load="False"

Summary

One of the mechanisms for lazy-loading x:Load="False" elements is via targeting the element in a VisualState Setter. This allows for rarely-used visual elements, for example a progress bar rectangle within a button, to be loaded only when needed. This could be done by defining, say, a "ProgressStates" visual state group, and loading the conditional element only in the "Progressing" state.

The issue is that these conditional elements can't interact meaningfully with other visual state groups without being loaded as a side effect. Continuing the above example, if the button had a "FocusStates" visual state group and wanted to style the rectangle's background when focused, it would need to have a Setter in the "Focused" state to set the background color. However, this would mean that entering the "Focused" state would always load the background rectangle, defeating the purpose of the conditionalization.

The proposal would be to allow a VisualState Setter to do a "best effort" assignment to a x:Load=false element. If the element is already realized, the assignment would "take," and if not, it'd take only once the element were realized. This would be an opt-in for VisualState Setter syntax, with the default being the current behavior of realizing the x:Load=false element.

Doing this would allow one VisualStateGroup to define the load behavior of these conditional elements, and others to act upon those elements opportunistically. The end result would be flexible control templates that become pay-for-play but can still be richly styled in markup.

Rationale

To emulate the above behavior, currently only the following options exist:

  1. Create completely separate visual styles for "ProgressingButton" and "NotProgressingButton," with the conditional elements present in one but absent in the other;
  2. Forego markup entirely and drive the visual states of the button by hand;
  3. Forego x:Load=false on the rarely-used elements.

None of these are particularly attractive alternatives. Option 1 potentially duplicates a lot of styling in the process (and combines into a matrix of styles depending on how many optional elements exist). Option 2 requires a lot of code-behind, reduces designer flexibility, and doesn't scale well either. Option 3 essentially gives up on any load-on-demand optimization offered by XAML.

This feature would make it much easier for developers to concisely author progressively-complex styling, allowing for more efficient use of the XAML runtime.

Scope

Capability Priority
This proposal will allow developers to declaratively defer a x:Load expression within a VisualState Setter Must

Important Notes

As a proposal, I've added a Boolean property, Setter.Defer. If set to True, it will not trigger x:Load realization for its target element, applying when the element is loaded only. Example usage is as follows. When the button using this template goes to the "Focused" state having never entered the "Progressing" state, the setter for "ConditionallyPresentRect.Background" would do nothing, as its associated element had not yet been realized. The formulation "Defer='True'" is what would suppress this realization. After entering the "Progressing" state, the Setter for "ConditionallyPresentRect.Visibility" would realize the element as this is a normal, non-deferring Setter. Thereafter, the setter for "Focused" state would work as expected.

<Grid x:Name="RootGrid">
  <VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="FocusStates">
      <VisualState x:Name="Unfocused" />
      <VisualState x:Name="Focused">
        <VisualState.Setters>
          <Setter Target="AlwaysPresentRect.Background" Value="Green" />
          <!-- Set the conditional background only if the element has been realized. -->
          <Setter Target="ConditionallyPresentRect.Background" Value="Blue" Defer="True" />
        </VisualState.Setters>
      </VisualState>
    </VisualStateGroup>
    <VisualStateGroup x:Name="ProgressStates">
      <VisualState x:Name="NotProgressing" />
      <VisualState x:Name="Progressing">
        <VisualState.Setters>
          <!-- In this visual state, force the conditional background to load. -->
          <Setter Target="ConditionallyPresentRect.Visibility" Value="Visible" />
        </VisualState.Setters>
      </VisualState>
    </VisualStateGroup>
  </VisualStateManager.VisualStateGroups>
  <StackPanel>
    <Rectangle x:Name="AlwaysPresentRect" Background="White" />
    <Rectangle x:Name="ConditionallyPresentRect" Background="Yellow" x:Load="False" />
  </StackPanel>
</Grid>

Note also that a deferred Setter would also defer setting properties on descendants of a x:Load=false element silently, and would apply when the ancestor is realized.

Open Questions

@JulianSelman JulianSelman added the feature proposal New feature proposal label Jun 4, 2019
@bschoepke
Copy link

bschoepke commented Jun 15, 2019

We've also identified this as a pain point on the Windows shell team. We generally avoid using VisualStateManager for elements with x:Load applied, which has been a big source of confusion for new team members ramping up on XAML, as it muddies the guidelines for when to use VisualStateManager vs. x:Bind. And it devalues VisualStates because often the same logical visual state for one of our controls is applied via a combination of VisualStateManager and x:Bind expressions.

@jevansaks jevansaks added area-Framework needs-triage Issue needs to be triaged by the area owners labels Nov 7, 2019
@chrisglein chrisglein added area-Styling needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) and removed needs-triage Issue needs to be triaged by the area owners labels Jan 30, 2020
@chrisglein
Copy link
Member

Sounds like a reasonable proposal. Agree that you'd need some additional information to know what setters should cause the element to be defer loaded in and what should leave it unloaded.

In the meantime, I think #2 may be your best option (drive the states by hand). Yes it's going to involve sacrifices to your designer experience, but at least with dropping down into code for everything you can structure it exactly the way you want.

@bpulliam bpulliam added team-Controls Issue for the Controls team and removed team-Framework labels Aug 22, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot added the needs-triage Issue needs to be triaged by the area owners label Aug 22, 2023
@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Oct 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Styling feature proposal New feature proposal needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) team-Controls Issue for the Controls team
Projects
None yet
Development

No branches or pull requests

5 participants