-
Notifications
You must be signed in to change notification settings - Fork 769
Description
Proposal: Improve the XAML styling system to target template attributes and states
Summary
Provide a simplier styling system within XAML that enables a user to customize animations, template properties and attributes, and visual states without the need to re-template or copy numerous resources to a dictionary.
Rationale
Styling or customizing XAML controls today, outside of properties on a control, is very dependent on the developer's knowledge of the control's template and our framework. Although having a deep knowledge of our system isn't a bad thing, it can lead to some unwanted workarounds when it comes to customization and branding our controls.
There are two major sticking points that developers can run into when customizing our controls:
- Desire to change visuals during a state (colors, attributes, ContentPresenter properties, etc.)
- Need to alter default template attributes (spacing, corner radius, fixed widths, etc.)
To do either of these the above customization, a developer would need to maintain ResourceDictionary(ies) with hundreds of resources (if changing colors and/or control properties), or take complete ownership of the control through re-templating (if changing attributes).
Having numerous resources to maintain during design changes is a lot of developer cost and overhead, while the re-templating option means a higher risk of breaking high contrast as well as tedious, file diff changes when we roll out new updates to our default control templates.
With so many ways to customize a control, it's almost impossible for us to enable a property for each permutation that a developer could need. Therefore, it would make more sense to simply improve our current styling system to enable more versatility.
Functional Requirements
# | Feature | Priority |
---|---|---|
1 | Easily access control template properties, components and attributes within a style | Must |
2 | Customize controls based on the template markup, not through resource keys or APIs | Must |
3 | Gracefully fallback or throw exceptions when template attribute markup has changed | Must |
4 | Anything not specified in the (non-template) style will default to their generic.xaml definition(s) | Must |
5 | Can target any visual state within a style | Must |
6 | Animations can be added, removed, or changed within a style | Must |
7 | Does not remove or make obsolete today's method of styling, instead offering a lighter weight alternative | Must |
8 | Intellisense supports styling system | Should |
Important Notes
Let's look at common styling scenarios and how we might be able to change the way we can style them.
Styling a Control Locally
Styling a control is simplified and it's easy to target the named parts within that control's template.
<RadioButton Content="RadioButton">
<RadioButton.Style>
<Style TargetType="RadioButton">
<Setter Path="Background" Value="White"/>
<Setter TargetName="CheckOuterEllipse" Property="Fill" Value="Green"/>
<Setter TargetName="CheckOuterEllipse" Property="Width" Value="18"/>
</Style>
</RadioButton.Style>
</RadioButton>
Changing Properties on Visual States
When a developer wants properties to changed only within a state, they can target that state directly without needing to specify the entire VisalState tree/manager.
<RadioButton Content="RadioButton">
<RadioButton.Style>
<Style TargetType="RadioButton">
<VisualState x:Name="PointerOver">
<Setter Path="Background" Value="White"/>
</VisualState>
<Setter TargetName="CheckOuterEllipse" Property="Fill" Value="Green"/>
<Setter TargetName="CheckOuterEllipse" Property="Width" Value="18"/>
</Style>
</RadioButton.Style>
</RadioButton>
Since the style defined above has no states for Pressed and Disabled, the button control in this case will use it's generic.xaml definitions for those states, and the PointerOver definition specified above for hover.
The more states defined in the style, the less is being defaulted to in generic.xaml.
Styles at a Page-Level
This is the same behavior today and I am calling it out as something we will maintain with this new styling system as well.
Just as you can define styles and templates at a page-level, this new styling system can also be defined there.
<Page.Resources>
<Style TargetType="RadioButton">
<Setter Path="Background" Value="White"/>
<Setter TargetName="CheckOuterEllipse" Property="Fill" Value="Green"/>
<Setter TargetName="CheckOuterEllipse" Property="Width" Value="18"/>
</Style>
</Page.Resources>
Specifying Animations
Animations specified using the improved styling method.
<Style TargetType="Button">
<VisualState x:Name="PointerOver">
<Storyboard>
<PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" />
</Storyboard>
</VisualState>
<Setter Path="Background" Value="White"/>
<Setter TargetName="ContentPresenter" Property="CornerRadius" Value="4"/>
</Style>
Styling in Code-Behind
This new styling method would also modify how we style controls from code-behind. Although similar to what can be done today, there would need to be a few changes to account for visual states and targeting template attributes.
//Create style and add a PointerOver state using a previously defined storyboard and setters
Style style = new Style(typeof(AppBarButton));
style.Setters.Add(new Setter(AppBarButton.ContentViewbox.Height, "14"));
style.VisualStates.Add(new VisualState("PointerOver")
{ Setters = mySetterBaseCollection, Storyboard = myStoryboard });
//Add that style to container (or page) resources
rootGrid.Resources.Add(typeof (AppBarButton), style);
Intellisense Support
Intellisense will also scan and pick up the named parts within a control's template and display them in a Setter's property option dropdown. This would remove the need to dig through generic.xaml and would greatly increase the speed of app development and productivity.
Using this Path attribute will also allow "dotting" into the named element within that template and accessing it's properties directly.
Open Questions/Issues
- In this model, when we do change our default template attributes, the styles relying on those x:Key names will no longer be valid.
Even if we do fallback gracefully and intellisense detects which attributes don't exist anymore, it could still be a bit tedious for the developer to update those attribute names individually. - This includes a major change to our VisualStates and how they're architected.
VisualStates as they are today were built to help immensely with VisualStudio's tooling features, something we would not want to regress if we were to modify how and where VisualStates can be defined. - How would this work or use XAML direct?