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()); }