Improve VisualState order and prevent sticky Focused visual state#27477
Improve VisualState order and prevent sticky Focused visual state#27477mattleibow wants to merge 9 commits into
Conversation
There was a problem hiding this comment.
Copilot reviewed 5 out of 7 changed files in this pull request and generated no comments.
Files not reviewed (2)
- src/Controls/tests/TestCases.HostApp/Issues/Issue19752.xaml: Language not supported
- src/Controls/src/Core/VisualElement/VisualElement.cs: Evaluated as low risk
Comments suppressed due to low confidence (2)
src/TestUtils/src/UITest.Appium/Actions/AppiumMouseActions.cs:221
- Ensure that the new MoveCursor and MoveCursorCoordinates commands are covered by tests.
CommandResponse MoveCursor(IDictionary<string, object> parameters)
src/TestUtils/src/UITest.Appium/HelperExtensions.cs:2282
- Ensure that the new MoveCursor methods are covered by tests.
public static void MoveCursor(this IApp app, string element)
|
@MartyIX had some wise words:
|
|
Thanks for the wise words @MartyIX, maybe this is a better order: var shouldFocus = IsFocused && IsEnabled;
// 1. unfocus first
if (!shouldFocus)
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Unfocused);
// 2. set basic states (normal/disabled)
if (!IsEnabled)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Disabled);
else if (!IsPointerOver)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
// 3. focus
if (shouldFocus)
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Focused);
// 4. end with pointer over
if (IsPointerOver)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.PointerOver);This is different to UWP/WPF/WinUI, so it may be better or it may cause people to get surprised coming from another XAML framework: // 1. set basic states (normal/disabled/pointer over)
if (!IsEnabled)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Disabled);
else if (IsPointerOver)
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.PointerOver);
else
VisualStateManager.GoToState(this, VisualStateManager.CommonStates.Normal);
// 2. override with focus
if (IsFocused && IsEnabled)
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Focused);
else
VisualStateManager.GoToState(this, VisualStateManager.FocusStates.Unfocused);Any thoughts? |
Could you explain how it is different exactly? I don't know the frameworks in detail. |
jsuarezruiz
left a comment
There was a problem hiding this comment.
Need to verify some related UITests checking the focused VisualState etc.

Example:
DisablingUnfocusedButtonMovesToDisabledState
Assert.That(App.FindElement("button2").GetText(), Is.EqualTo("Disabled"))
Expected string length 8 but was 11. Strings differ at index 0.
Expected: "Disabled"
But was: "PointerOver"
@MartyIX I updated the comment with the WinUI way so it can be seen side-by-side |
|
It looks good to me. :-) I still wonder though how will one implement styling for a button like this:
I think that one can make it somehow work with triggers (doc). But not with visual styles (doc). Is that right? It's not like the scenario is super-useful. The question is more about API design and perhaps even for user-defined visual styles and their composition. |
ee4cb2f to
4e36d9f
Compare
|
/rebase |
4e36d9f to
a1b0d56
Compare
|
/rebase |
a1b0d56 to
30161dc
Compare
|
Looks like it has been resolved |
How was it resolved? |
|
@MartyIX do you think this PR is still needed? I reopened it, but it might be better to create a new PR so I can try to prioritize it. This one looks like it went stale and accumulated conflicts, so I closed it for now. |
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 27477Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 27477" |
This PR adds new API for focused state: https://github.com/dotnet/maui/pull/27477/files#diff-6dd0e5f770ae939379098cbfe8654a00ebc4e9d0bf1c08c3bf9ce3e33a92de8fR24-R27 I don't know about any PR that does that. But the PR is stale, that's true. Matt is probably out of time to finish it. |
|
/review -b feature/refactor-copilot-yml |
|
/review -b feature/refactor-copilot-yml |
This comment has been minimized.
This comment has been minimized.
kubaflo
left a comment
There was a problem hiding this comment.
Could you please resolve conflicts?
|
/review -b feature/enhanced-reviewer -p windows |
This comment has been minimized.
This comment has been minimized.
|
/review -b feature/enhanced-reviewer -p android |
|
|
|
/review rerun |

Description of Change
Alternative to #19752
While reviewing @MartyIX's PR #19812 I discovered that the code for switching to the
FocusedandUnfocusedstates was all dependent on the control being enabled. What this results in that if you have a focused button and the visual state was some sort of border, disabling the button will not actually switch to unfocused and the visual state will remain with the border that was added when it got focused.This PR originally copied the code logic from WinUI: https://github.com/microsoft/microsoft-ui-xaml/blob/ffe33f9b7d0e9f5a2ca3330d0ce329f09dff092b/src/dxaml/xcp/dxaml/lib/Button_Partial.cpp#L29-L60 but I have updated it to follow maybe a better visual state order. This new way is to make sure the unfocus happens first and the pointer over happens last.
For the issue in #19752, the actual reason things are wrong is not because the states are set wrong, but rather because the focus states are in the same group as the pointer over state. This means that the button can either be focused or be pointer over.
The correct way to have all these states working is to use multiple groups:
This can also be seen in other controls such as the WinUI combo box (the Button does not use a state but rather the OS focus border): https://github.com/microsoft/microsoft-ui-xaml/blob/ffe33f9b7d0e9f5a2ca3330d0ce329f09dff092b/src/controls/dev/ComboBox/ComboBox_themeresources.xaml#L472 It is also in the docs: https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.controls.control.usesystemfocusvisuals?view=winrt-26100#examples
This is the docs for WinUI to do focus states:
Another result of not having multiple groups is that sometimes unexpected things happen. If you are missing the focus states, then nothing happens when you change states. And, if you have the focus states in the same group as normal, the normal state will never apply since it will either be focused or unfocused and normal will be overwritten.
Issues Fixed
I was not able to find an open issue with the focus states "sticking" when disabling. And the issues that I have seen are just VSM improperly configured.
Maybe these: