From 2e2cbfcf642d82736ad416cbd8df34e7d487205e Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Fri, 16 May 2025 16:30:51 -0500 Subject: [PATCH] PRE-MERGE CmdPal: Auto-focus first content #39414 Squashed commit of the following: commit f64bfd1c87f48ce64fe9d6646dc002e86d19b0d7 Author: Mike Griese Date: Wed May 14 06:08:50 2025 -0500 don't do the focus thing if there's a bunch of controls commit 01250bad73dff72f4d9a4312acd8a68382cdea98 Author: Mike Griese Date: Tue May 13 14:25:21 2025 -0500 instead of checking individual controls, use AllowFocusOnInteraction commit d7432d661e458c14a1d3db525634011c6b33e3e1 Author: Mike Griese Date: Tue May 13 14:07:59 2025 -0500 Try to focus forms on load when a content form first loads, try to focus it. This does work for a single control on a page, but doesn't work great if there's more than one --- .../ContentPageViewModel.cs | 3 + .../ContentViewModel.cs | 1 + .../Controls/ContentFormControl.xaml.cs | 56 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentPageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentPageViewModel.cs index f8b0e90834..e7a3145e0a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentPageViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentPageViewModel.cs @@ -79,6 +79,9 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext throw; } + var oneContent = newContent.Count == 1; + newContent.ForEach(c => c.OnlyControlOnPage = oneContent); + // Now, back to a UI thread to update the observable collection DoOnUiThread( () => diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentViewModel.cs index 9ebf5495fa..21d2d6748f 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentViewModel.cs @@ -7,4 +7,5 @@ namespace Microsoft.CmdPal.UI.ViewModels; public abstract partial class ContentViewModel(WeakReference context) : ExtensionObjectViewModel(context) { + public bool OnlyControlOnPage { get; internal set; } } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs index 68209d750a..b2afe78dfb 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/ContentFormControl.xaml.cs @@ -5,7 +5,9 @@ using AdaptiveCards.ObjectModel.WinUI3; using AdaptiveCards.Rendering.WinUI3; using Microsoft.CmdPal.UI.ViewModels; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; namespace Microsoft.CmdPal.UI.Controls; @@ -96,11 +98,65 @@ public sealed partial class ContentFormControl : UserControl if (_renderedCard.FrameworkElement != null) { ContentGrid.Children.Add(_renderedCard.FrameworkElement); + + // Use the Loaded event to ensure we focus after the card is in the visual tree + _renderedCard.FrameworkElement.Loaded += OnFrameworkElementLoaded; } _renderedCard.Action += Rendered_Action; } + private void OnFrameworkElementLoaded(object sender, RoutedEventArgs e) + { + // Unhook the event handler to avoid multiple registrations + if (sender is FrameworkElement element) + { + element.Loaded -= OnFrameworkElementLoaded; + + if (!ViewModel?.OnlyControlOnPage ?? true) + { + return; + } + + // Focus on the first focusable element asynchronously to ensure the visual tree is fully built + element.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () => + { + var focusableElement = FindFirstFocusableElement(element); + focusableElement?.Focus(FocusState.Programmatic); + }); + } + } + + private Control? FindFirstFocusableElement(DependencyObject parent) + { + var childCount = VisualTreeHelper.GetChildrenCount(parent); + + // Process children first (depth-first search) + for (var i = 0; i < childCount; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + + // If the child is a focusable control like TextBox, ComboBox, etc. + if (child is Control control && + control.IsEnabled && + control.IsTabStop && + control.Visibility == Visibility.Visible && + control.AllowFocusOnInteraction) + { + return control; + } + + // Recursively check children + var result = FindFirstFocusableElement(child); + if (result != null) + { + return result; + } + } + + return null; + } + private void Rendered_Action(RenderedAdaptiveCard sender, AdaptiveActionEventArgs args) => ViewModel?.HandleSubmit(args.Action, args.Inputs.AsJson()); }