mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-22 10:07:37 +00:00
What the title says. 😄
Rather than relying on the potentially overloaded `!=` or `==` operators
when checking for null, now we'll use the `is` expression (possibly
combined with the `not` operator) to ensure correct checking. Probably
overkill for many of these classes, but decided to err on the side of
consistency. Would matter more on classes that may be inherited or
extended.
Using `is` and `is not` will provide us a guarantee that no
user-overloaded equality operators (`==`/`!=`) is invoked when a
`expression is null` is evaluated.
In code form, changed all instances of:
```c#
something != null
something == null
```
to:
```c#
something is not null
something is null
```
The one exception was checking null on a `KeyChord`. `KeyChord` is a
struct which is never null so VS will raise an error when trying this
versus just providing a warning when using `keyChord != null`. In
reality, we shouldn't do this check because it can't ever be null. In
the case of a `KeyChord` it **would** be a `KeyChord` equivalent to:
```c#
KeyChord keyChord = new ()
{
Modifiers = 0,
Vkey = 0,
ScanCode = 0
};
```
163 lines
5.4 KiB
C#
163 lines
5.4 KiB
C#
// Copyright (c) Microsoft Corporation
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
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;
|
|
|
|
public sealed partial class ContentFormControl : UserControl
|
|
{
|
|
private static readonly AdaptiveCardRenderer _renderer;
|
|
private ContentFormViewModel? _viewModel;
|
|
|
|
// LOAD-BEARING: if you don't hang onto a reference to the RenderedAdaptiveCard
|
|
// then the GC might clean it up sometime, even while the card is in the UI
|
|
// tree. If this gets GC'ed, then it'll revoke our Action handler, and the
|
|
// form will do seemingly nothing.
|
|
private RenderedAdaptiveCard? _renderedCard;
|
|
|
|
public ContentFormViewModel? ViewModel { get => _viewModel; set => AttachViewModel(value); }
|
|
|
|
static ContentFormControl()
|
|
{
|
|
// We can't use `CardOverrideStyles` here yet, because we haven't called InitializeComponent once.
|
|
// But also, the default value isn't `null` here. It's... some other default empty value.
|
|
// So clear it out so that we know when the first time we get created is
|
|
_renderer = new AdaptiveCardRenderer()
|
|
{
|
|
OverrideStyles = null,
|
|
};
|
|
}
|
|
|
|
public ContentFormControl()
|
|
{
|
|
this.InitializeComponent();
|
|
var lightTheme = ActualTheme == Microsoft.UI.Xaml.ElementTheme.Light;
|
|
_renderer.HostConfig = lightTheme ? AdaptiveCardsConfig.Light : AdaptiveCardsConfig.Dark;
|
|
|
|
// 5% BODGY: if we set this multiple times over the lifetime of the app,
|
|
// then the second call will explode, because "CardOverrideStyles is already the child of another element".
|
|
// SO only set this once.
|
|
if (_renderer.OverrideStyles is null)
|
|
{
|
|
_renderer.OverrideStyles = CardOverrideStyles;
|
|
}
|
|
|
|
// TODO in the future, we should handle ActualThemeChanged and replace
|
|
// our rendered card with one for that theme. But today is not that day
|
|
}
|
|
|
|
private void AttachViewModel(ContentFormViewModel? vm)
|
|
{
|
|
if (_viewModel is not null)
|
|
{
|
|
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
|
|
}
|
|
|
|
_viewModel = vm;
|
|
|
|
if (_viewModel is not null)
|
|
{
|
|
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
|
|
|
|
var c = _viewModel.Card;
|
|
if (c is not null)
|
|
{
|
|
DisplayCard(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
|
{
|
|
if (ViewModel is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.PropertyName == nameof(ViewModel.Card))
|
|
{
|
|
var c = ViewModel.Card;
|
|
if (c is not null)
|
|
{
|
|
DisplayCard(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DisplayCard(AdaptiveCardParseResult result)
|
|
{
|
|
_renderedCard = _renderer.RenderAdaptiveCard(result.AdaptiveCard);
|
|
ContentGrid.Children.Clear();
|
|
if (_renderedCard.FrameworkElement is not 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 is not null)
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void Rendered_Action(RenderedAdaptiveCard sender, AdaptiveActionEventArgs args) =>
|
|
ViewModel?.HandleSubmit(args.Action, args.Inputs.AsJson());
|
|
}
|