microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.35k stars 677 forks source link

Suggestion: Add Compact Dictionary to WinUI 2.1 #98

Closed mdtauk closed 5 years ago

mdtauk commented 5 years ago

CompactMode Xaml Property

Summary

Compact Styles bundled with Microsoft.Xaml.UI and applied via a property rather than a separate ResourceDictionary

Rationale

At present Microsoft has pushed the idea of Compact styles for controls being handled by adding a Resource Dictionary to the app. But with Microsoft.Xaml.UI potentially replacing the default UWP controls - perhaps it would be a good idea to handle the compact styles in the controls themselves. Have it be an option that can be inherited by all controls when a CompactMode= True is set on a parent panel, page or app.xaml level.

Functional Requirements

Usage Examples

Detailed Feature Design

Some examples: <Grid Width="*" Height="300" CompactMode="True"> ... </Grid>

<TextBox Header="Username" HeaderPosition="Left" CompactMode="True" />

This could include one or more of:

Open Questions

Please pay attention to DatePickerFlyout and TimePickerFlyout problem which is described in PR 351

Release Checklists

Prerelease readiness

Stable release readiness

kmgallahan commented 5 years ago

But with Microsoft.Xaml.UI potentially replacing the default UWP controls

The existing WinUI controls are moving to this repo, not the other way around.

Have it be an option that can be inherited by all controls when a CompactMode= True is set on a parent panel, page or app.xaml level.

Subverts the existing XAML theme system.

Also impedes usage of more than 2 options, as already exists in the Mail app:

annotation 2018-12-15 153557

Avoid loading multiple templates in memory

without risk of outdated Resource Dictionaries and drift into inconsistent usage.

Per the documentation linked above:

The Windows Runtime doesn't use these physical files for runtime lookup. That's why they are specifically in a DesignTime folder, and they aren't copied into apps by default. Instead, these resource dictionaries exist in memory as part of the Windows Runtime itself, and your app's XAML resource references to theme resources (or system resources) resolve there at runtime.

The default themes are already in memory, and reinforcing usage of the XAML theme system would reduce "drift".

mrlacey commented 5 years ago

When compact mode relates to the spacing between controls, what would happen if, for instance, you had two TextBoxes in StackPanel and one had this enabled but the other didn't? The predictability of what should happen isn't clear and so to implement this would require defining what should happen in all scenarios.

perhaps it would be a good idea to handle the compact styles in the controls themselves.

This sounds more of a possible option, rather than a rationale for making this change.

mdtauk commented 5 years ago

The existing WinUI controls are moving to this repo, not the other way around.

That is the point I was making. There would no longer be a Windows.Xaml.UI.Controls part of the framework, it would all be the separate Microsoft.Xaml.UI

Subverts the existing XAML theme system.

Also impedes usage of more than 2 options, as already exists in the Mail app:

Microsoft's guidance specifies there is the default density of UWP controls, and then an optional Compact ResourceDictionary you can opt to include in your app. The Mail app is using Office's custom controls and XAML implementation - so is not the "official" way to do it.

If Microsoft's guidance were to develop, instead of a Boolean property, it could become an Enum.

The default themes are already in memory, and reinforcing usage of the XAML theme system would reduce "drift".

If the controls will eventually be lifted out of the UWP framework, and the Windows UI Library becomes the new way to use XAML controls - then what is or isn't stored in memory will change AFAIK.


When compact mode relates to the spacing between controls, what would happen if, for instance, you had two TextBoxes in StackPanel and one had this enabled but the other didn't? The predictability of what should happen isn't clear and so to implement this would require defining what should happen in all scenarios.

perhaps it would be a good idea to handle the compact styles in the controls themselves.

This sounds more of a possible option, rather than a rationale for making this change.

The controls currently have default values provided by the control templates so this need not be a complicated issue, as it will work in the same way as applying a Style to a single control now, or applying a Resource Dictionary on a whole page of controls.

LucasHaines commented 5 years ago

Thanks for bringing this up!

We do want to deliver a more robust solution for Compact mode that works for all released version of Windows 10. Not just the latest. While we work out the longer term version that enables current and down-level support. We have two options that would love feedback on.

  1. Ship the ResourceDictionary in WinUI and point customers to the this repo to download with instructiuons on how to merge at the app, page, or grid level.
  2. Use attached properties/behavior as a work around.

Below is the feature request I was writing before this was submitted :)

Summary

How best should we delivery the Compact sizes for controls to enable dense layout to all supported versions of Windows 10.

Rationale

In the October 2018 Update (17763) We introduced the new Standard sizes for controls and spacing which reduced the amount of white space on average by 33% in controls. We created the infrastructure for the Compact Density sizes at that time. We are working to bring more functionality down-level and into the WinUI Nuget Package. When features require XAML language updates, this creates a dependency on the latest release. A key goal for the Density effort is to bring balance and size flexibility to users not running the latest version of Windows 10. We have a couple of delivery options and we want to engage the community for feedback.

Functional Requirements

# Feature Priority
1 Easily enable Fluent Compact Sizing on all released versions of Windows 10 Must
2 Offer a short-term solution for Compact sizing in UWP Must
3 Enable planning effort for long-term solution for Compact sizing in UWP. Must
mdtauk commented 5 years ago

The ResourceDictionary method is the current implementation, but bundling it into the Windows UI Library makes sense as it will be a tested and verified method which maintains respect for Accessibility etc.

But as a long term goal, making it a property you can apply would be the easiest way to do it, and the controls would handle it on themselves, or inherited from the parent panel.

I am not sure if there is a way to reference a ResourceDictionary that is within a referenced binary. Doing something like: <ResourceDictionary Source="Microsoft.UI.Xaml.Controls.CompactMode" /> Rather than asking developers to copy and paste a xaml file into their project.

JustinXinLiu commented 5 years ago

I like the idea, however I'd go with an enum property instead of bool so it could support more than two modes.

mdtauk commented 5 years ago

Enum is good then, as more options can be added if the Fluent Team so wished in the future.

deanchalk commented 5 years ago

For me the big problem with using something like an enum and building this into the controls themselves is that it violates the 'look-less' principal of XAML controls. The control's implementation should have no reference to it's 'look'. Also, what if you want to restyle a template in Blend? Will these Control Templates end up massive and full of style triggers depending on this enum? A much better way in my opinion is to achieve this with Resource Dictionaries, and maybe have an app-level enum to switch dictionaries - like this:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.DensityDictionaries>
            <ResourceDictionary x:Key="Standard">
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml.Controls.StandardMode" />
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Compact">
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml.Controls.CompactMode" />
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </ResourceDictionary.DensityDictionaries>
    </ResourceDictionary>
</Application.Resources>
michael-hawker commented 5 years ago

I know some apps/games like to provide the user with options for different themes/skins for their app. It'd be great to think about expanding the notion of these different themes to provide a bit broader helpers for letting users choose between light/dark/A/B/C/etc... and Compact/Spacious/Large etc...

Do we need something like the VisualStateManager for theme resources?

JustinXinLiu commented 5 years ago

Using only resource dictionaries is way too limited. What if you need smaller buttons and text boxes on a particular page? What if a button on a page is meant to be bigger than the rest?

The proposed size mode enum is merely a UI flag that gives the control's style the ability to select from different combinations of design attributes such as font size, width, height, margin, padding, etc.

deanchalk commented 5 years ago

The purpose of compact sizing is to increase information density application-wide. Like switching between light them and dark theme - its an application-wide change to the user interface. If you want bigger buttons here and smaller buttons there, then you can override standard styles and do that - as we've been doing with XAML since 2006. Enum's is very limiting, and ResourceDictionaries - the canonical way of skinning the UI - is absolutely the correct way to go. Every XAML Language is built on the pricipal of 'lookless' controls with every XAML language implementing the 'look' entirely within resource dictionaries. I dont even know why we would entertain the concept of using a 'look-mode' switch on controls - it breaks all of the existing UI paradigms and is a limited solution. As someone who has been building XAML UI's every day since 2006, working on over 20 big enterprise WPF and UWP projects I can say with a great deal of confidence that resource dictionaries is the only and correct way forward. Dont forget that a lot of Controls are composite controls - like a combobox for example. With ResourceDictionaries the textbox inside the combobox will always be correct. But in 'enum-switch mode' if the combobox is in compact mode, then we need a trigger to switch the entire Control's Template to the 'compact version of all of the child controls. This would be a nightmare, unless we were to create some hacky new 'compact mode' inheritance model for Controls, which means all controls have to be re-written'. Resource dictionaries avoid all these styling and control template problems, which is why they are the only solution for this kind of problem.

ResourceDictionaries wont required the entire control library to be re-written and re-templated ResourceDictionaries are a solution that could be implemented very easily and quickly, without any breaking changes - we simply take a copy of the existing UWP generic.xaml and themeresource.xaml dictionaries and change all of the size/margin/padding etc values and tweak them until it looks good.

To be honest, why is there a massive need for a so-called compact style? As someone who spoke extensively in interviews with Microsoft about this before it was announced, it is about creating great desktop experiences, with the correct information density. The 'mobile-first' sentiments around UWP have been ditched and UWP is supposed to replace WPF as the native client platform for desktop development, but cannot until the information density problem is solved. Once 'compact mode' is available, it will be the only mode used, so lets get this right, otherwise us seasoned desktop developers will have to throw our hands up in despair yet again because Microsoft cant engage in any joined-up thinking.

JustinXinLiu commented 5 years ago

In your ComboBox example, we can use TemplateBinding to pass down the enum flag from parent to all its templated children inside its default style. Clean and simple.

The flag is not for solving everything, but on top of app-level resource dictionaries, it could be a useful UI attribute that overrides the styling at the control level, without the need of drilling down to complex templates.

LucasHaines commented 5 years ago

Thanks for the great discussion! @deanchalk you have outlined the reason why we went with the ResourceDictionaries. You can merge dictionaries at the app, page, and grid level. For overriding sizes at the control level, this seems like a scoped scenario. How to you see the enum being a better solution then setting style for that specific control?

mdtauk commented 5 years ago

Thanks for the great discussion! @deanchalk you have outlined the reason why we went with the ResourceDictionaries. You can merge dictionaries at the app, page, and grid level. For overriding sizes at the control level, this seems like a scoped scenario. How to you see the enum being a better solution then setting style for that specific control?

I think my fear is that by making it an external resource, it adds a complication for those wanting to implement it, as opposed to something that is managed by the Windows UI Library, where you can apply it to a panel, page or app to achieve the compact look.

A ResourceDictionary designed for an older version of WinUI may still circulate and be mismatched. It wont automatically keep in sync with updates to the WinUI Nuget package.

Also it could lead to inconsistencies as others may start editing the values without specific needs - leading to more complaints of inconsistent UI with Windows apps, and between Apps and the OS.

And if after all that, developers wish to customise their control templates for when they are in CompactMode - this should be achievable via editing Templates which are applied when CompactMode is set to a value, so the controls hold templates for Default, Compact, XXXXXX states that are used and switched as the value changes.

An app which uses the standard/default templates for the controls, could display a content dialog with CompactMode set and have a more dense UI for editing values for instance, or an app like the Mail app, could offer a Default, Compact, Spacious option, which changes the app window's CompactMode - making it easy for developers to implement.

deanchalk commented 5 years ago

@mdtauk wether you use enums or resource dictionaries each control is still going to effectively have 3 templates (which exist in resource dictionaries), your just putting the burden of switching at the control level, rather than at the application level, which has no benefit. Compact mode or otherwise is about creating a better app experience on different kit, or because the application needs to be designed for a high-density scenario like financial trading. Being able to switch compact mode on or off at the control level (or even programmatically) just makes no sense. Having an ‘old’ resource dictionary knocking about is the same as having old nuget packages in a solution, there are pro’s and cons. Not everyone is allowed to update stuff willy-nilly in large enterprise apps as the risks are too high - so it would be a plus. At the very least it’s a non-issue.

mdtauk commented 5 years ago

@mdtauk wether you use enums or resource dictionaries each control is still going to effectively have 3 templates (which exist in resource dictionaries), your just putting the burden of switching at the control level, rather than at the application level, which has no benefit. Compact mode or otherwise is about creating a better app experience on different kit, or because the application needs to be designed for a high-density scenario like financial trading. Being able to switch compact mode on or off at the control level (or even programmatically) just makes no sense. Having an ‘old’ resource dictionary knocking about is the same as having old nuget packages in a solution, there are pro’s and cons. Not everyone is allowed to update stuff willy-nilly in large enterprise apps as the risks are too high - so it would be a plus. At the very least it’s a non-issue.

Having the control handle the controlling of how the control renders, takes the burden away from the developer in multiple ways. The resource dictionaries are included in the package and so will remain consistent for anyone using the nuget package, and will match the version of the package being used.

The property could be applied to a parent panel and be inherited by all child controls. Page Level, or in the App.xaml file.

To the user of the app, the developer can easily choose the right presentation of those controls by including the ResourceDictionary found somewhere on MSDN, or by setting the property at the right level needed.

The only difference is what is less work for the developer, and more consistent for the user. I would argue that making it a simple XAML property to set is easier and more consistent, than making developers search online for the right version of the XAML for the version of the nuget package they have to use, include it in the project's assets, and merging the dictionary into the app.

deanchalk commented 5 years ago

@mdtauk - how could this be inherited from the parent panel? This kind of dependency property wouldn't be inheritable - especially inside DataTemplates - no?

mdtauk commented 5 years ago

@mdtauk - how could this be inherited from the parent panel? This kind of dependency property wouldn't be inheritable - especially inside DataTemplates - no?

Well as this is a proposal I would suggest making sure it is inheritable in the same way FontFamily, FontSize properties are inherited from a parent, as is the new ThemeShadow in that applying one to the grid, applies to the items in the grid.

deanchalk commented 5 years ago

But property value inheritance is fickle. Some things are encapsulation boundaries like Frame, and DataTemplate, so its more complex than you suggest. Also property value inheritance is inefficient as the leaf controls need to traverse all the way up the logical tree to get the correct value. Also, this needs a deep refactoring of all controls, whereas the ResourceDictionary solution works today. Having controls full of triggers could cause a lot of problems. what happens when 'compact mode' requires a whole new template, and the control has to switch templates. Of course this compact mode enum will be a dependency property, and therefore can be changed dynamically which could be very problematic for a lot of the performance engineering that has been undertaken by 3rd party control providers. Talking of this - are we expecting 3rd party control providers (who will have to support this too) to re-write all of their controls? Mist of the projects I work on involve a lot of custom control development, so my clients will have to pay me more to develop a whole dynamic control-morphing capability, rather than just supply a resource dictionary that suits their UI mode ? I can see absolutely no benefit of breaking the way this stuff has always been done (with Resource Dictionaries) by inventing some new mechanism that requires the entire world to re-engineer their control assets and apps. If there was a compelling advantage to this, I might have a different point of view, but I really dont see this as the correct solution

JustinXinLiu commented 5 years ago

We are not saying not to have resource dictionaries. They are the easiest when you want app-level styling. But when dealing with complex UI, just imagine you need to include this instead of a simple property change...

<SomeControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.DensityDictionaries>
            <ResourceDictionary x:Key="Compact">
                <ResourceDictionary.MergedDictionaries>
                    <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml.Controls.SomeMode" />
                </ResourceDictionary.MergedDictionaries>
            </ResourceDictionary>
        </ResourceDictionary.DensityDictionaries>
    </ResourceDictionary>
</SomeControl.Resources>

So first the attach property solution to me sounds much neater.

  1. Use attached properties/behavior as a work around.

I understand that having a property on all controls is much harder to implement as it affects all default templates, but I still like the idea because it could potentially make writing complex UI a lot easier.

Imagine you need to build a media player where a Play button could be the biggest, Forward and Backward a little bit smaller, and the other buttons the smallest; or when you need to make some controls responsive and all you have to do is to change one property in adaptive triggers. To me the less the developers have to deal with styles and templates, the better.

Also, I don't see why having this property would break any 3rd party controls. If an existing control doesn't support it, it will simply ignore it and only provide one size.

LucasHaines commented 5 years ago

Everyone, Thanks for the great discussion on this effort. I think what it boils down to is flexibility. If we implement a enum or even extend the scoped color resources API to support to more values. We are not going to remove the ability to merge dictionaries. it's clear from this discussion that there is demand for a more robust implementation of compact and size overrides (not a shocker ;)). @mdtauk Do your mind if a re-write/update the feature proposal to accommodate the discussion in this thread?

mdtauk commented 5 years ago

I am very happy for the suggestion to be updated. It was intended to be a discussion topic to find a better way to implement the goal of making it super simple to use the Compact templates for controls. @LucasHaines

deanchalk commented 5 years ago

Can you prioritize the ResourceDictionary for compact mode that can be used with the current controls. I have several big enterprise WPF apps I'm trying to move over to UWP and when I spent time talking to Microsoft a year ago we agreed that the whole reason for compact mode in the first place is to support this migration scenario. The enum or bool option isnt going to be relevant as its just going to be compact UI throughout the app - so its optimized for high-density desktop (like WPF). This is the main blocker for UWP migration so I would appreciate if a ResourceDictionary could be the first step. Thanks

LucasHaines commented 5 years ago

@deanchalk Absolutely this work will not detract from the ResourceDictionary solution at all.

mdtauk commented 5 years ago

I would like to suggest that the ResourceDictionary for the current version of Microsoft.UI.Xaml be included in the package, and perhaps encourage devs to apply it via the namespace instead of bundling the xaml file into the app's resources.

This will at least help save devs a search for the right XAML file to download.

<ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml.Controls.Compact" />

LucasHaines commented 5 years ago

Hey @deanchalk (and everyone) are interested in testing the new CompactResource dictionary? We included this in the latest preview WinUI image. We identified a bug so it only works at Page.Resoruces and Grid.Resources, but we would like to get some customers to look at it too.

<Page.Resources>
    <ResourceDictionary Source="ms-appx:///Microsoft.UI.Xaml/DensityStyles/Compact.xaml" />
</Page.Resources>
msft-github-bot commented 5 years ago

:tada:This issue was addressed in #456, which has now been successfully released as Microsoft.UI.Xaml v2.1.190405004.:tada:

Handy links:

adrientetar commented 5 years ago

@LucasHaines, I'm trying the Compact Dictionary in my XAML but it looks like it can't find it:

image

I'm referencing it from a Grid placed in a UserControl, is there any import I should do in order to be able to reference it? I'm on 2.2.190416008-prerelease

jevansaks commented 5 years ago

The intellisense may not be able to find it because of how it's packaged in the .pri file. But do you get an error at runtime?

adrientetar commented 5 years ago

Seems I just needed to rebuild, sorry for the noise.

mdtauk commented 5 years ago

Earlier today I was made aware of a Microsoft project called FastDNA

It appears to be a React based Web Component library which is based on Fluent Design. Looking at the Button control, there is a Density option which moves between -3 to +3 https://explore.fastdna.net/components/button/

Right now the plan is to have a Default density, and a Compact density.

I would be curious to know what kinds of plans the Windows design team and WinUI 3.0 may have to support more densities?

There could be Default, Compact, and Legacy - Legacy matching it's control sizing to the Win32 and WinForms controls perhaps? It could be useful when mixing and matching Win32 and WinUI 3.0 via XamlIslands in a single form.

LucasHaines commented 5 years ago

at the moment we have nothing else on the road map to support more level's of density. Part of the density scope was to replace all hard coded values with resource keys. Which now allows you to have greater flexibility over the look and feel of the controls. Would it be helpful we documented how to match the Win32 styles with a resource dictionary?

mdtauk commented 5 years ago

If by document, you mean include a ResourceDictionary to match Win32 metrics...

The Windows shell team may have some thoughts, what with CoreOS in development, with new shells

chigy commented 5 years ago

@LucasHaines , going through items to make sure we have doc coverage. Was there any new code that got published that require a documentation here?

chigy commented 5 years ago

@LucasHaines , pinging again as I have not heard about the documentation update.

LucasHaines commented 5 years ago

@chigy The documentation went live when the Nuget packaged was released. https://docs.microsoft.com/en-us/windows/uwp/design/style/spacing

chigy commented 5 years ago

Moving to archive