Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Screenbox/Pages/MainPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:strings="using:Screenbox.Strings"
xmlns:triggers="using:Screenbox.Triggers"
muxc:BackdropMaterial.ApplyToRootOrPageBackground="True"
Loaded="MainPage_Loaded"
mc:Ignorable="d">
Expand Down Expand Up @@ -412,9 +413,10 @@

<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="WindowActivationStates">
<VisualState x:Name="Activated" />

<VisualState x:Name="Deactivated">
<VisualState.StateTriggers>
<triggers:WindowActivationModeTrigger ActivationMode="Deactivated" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="AppTitleText.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" />
<Setter Target="NavView.IsBackEnabled" Value="False" />
Expand Down
18 changes: 0 additions & 18 deletions Screenbox/Pages/MainPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ public MainPage()
// For example, when the app moves to a screen with a different DPI.
coreTitleBar.LayoutMetricsChanged += CoreTitleBar_LayoutMetricsChanged;

// Register a handler for when the window changes focus
Window.Current.CoreWindow.Activated += CoreWindow_Activated;

NotificationView.Translation = new Vector3(0, 0, 16);

_pages = new Dictionary<string, Type>
Expand Down Expand Up @@ -81,21 +78,6 @@ private void CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sende
RightPaddingColumn.Width = new GridLength(Math.Max(sender.SystemOverlayLeftInset, sender.SystemOverlayRightInset));
}

/// <summary>
/// Change the <see cref="VisualState"/> depending on whether the app is active or inactive.
/// </summary>
private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs args)
{
if (args.WindowActivationState == CoreWindowActivationState.Deactivated)
{
VisualStateManager.GoToState(this, "Deactivated", true);
}
else
{
VisualStateManager.GoToState(this, "Activated", true);
}
}

protected override void OnNavigatedTo(NavigationEventArgs e)
{
PlayerFrame.Navigate(typeof(PlayerPage), e.Parameter);
Expand Down
1 change: 1 addition & 0 deletions Screenbox/Screenbox.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Secrets.cs" />
<Compile Include="Services\ResourceService.cs" />
<Compile Include="Triggers\WindowActivationModeTrigger.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
Expand Down
102 changes: 102 additions & 0 deletions Screenbox/Triggers/WindowActivationModeTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using Windows.UI.Core;
using Windows.UI.Xaml;

namespace Screenbox.Triggers;

/// <summary>
/// Represents a declarative rule that applies visual states based on the <see cref="CoreWindow.ActivationMode"/> property.
/// </summary>
/// <remarks>
/// Use WindowActivationModeTriggers to create rules that automatically triggers a VisualState change when
/// the window is a specified activation state. When you use WindowActivationModeTriggers in your XAML markup,
/// you don't need to handle the <see cref="CoreWindow.Activated"/> event and call <see cref="VisualStateManager.GoToState"/> in your code.
/// </remarks>
/// <example>
/// This example shows how to use the <see cref="VisualState.StateTriggers"/> property with an <see cref="WindowActivationModeTrigger"/>
/// to create a declarative rule in XAML markup based on the activation state of the window.
/// <code lang="xaml">
/// &lt;Grid&gt;
/// &lt;StackPanel&gt;
/// &lt;TextBlock x:Name="FirstText"
/// Foreground="{ThemeResource TextFillColorPrimaryBrush}"
/// Text="This is a block of text. It is the 1st text block." /&gt;
/// &lt;TextBlock x:Name="LastText"
/// Foreground="{ThemeResource TextFillColorPrimaryBrush}"
/// Text="This is a block of text. It is the 2nd text block." /&gt;
/// &lt;/StackPanel&gt;
/// &lt;VisualStateManager.VisualStateGroups&gt;
/// &lt;VisualStateGroup&gt;
/// &lt;VisualState&gt;
/// &lt;VisualState.StateTriggers&gt;
/// &lt;!-- VisualState to be triggered when the activation state of the window is Deactivated. --&gt;
/// &lt;local:WindowActivationModeTrigger ActivationMode="Deactivated" /&gt;
/// &lt;/VisualState.StateTriggers&gt;
///
/// &lt;VisualState.Setters&gt;
/// &lt;Setter Target="FirstText.Foreground" Value="{ThemeResource TextFillColorDisabledBrush}" /&gt;
/// &lt;Setter Target="LastText.Opacity" Value="0.4" /&gt;
/// &lt;/VisualState.Setters&gt;
/// &lt;/VisualState&gt;
/// &lt;/VisualStateGroup&gt;
/// &lt;/VisualStateManager.VisualStateGroups&gt;
/// &lt;/Grid&gt;
/// </code>
/// </example>
[Windows.Foundation.Metadata.ContractVersion(typeof(Windows.Foundation.UniversalApiContract), 327680u)]
public sealed class WindowActivationModeTrigger : StateTriggerBase
{
private readonly CoreWindow _coreWindow;

/// <summary>
/// Identifies the <see cref="ActivationMode"/> dependency property.
/// </summary>
public static readonly DependencyProperty ActivationModeProperty = DependencyProperty.Register(
nameof(ActivationMode), typeof(CoreWindowActivationMode), typeof(WindowActivationModeTrigger), new PropertyMetadata(CoreWindowActivationMode.None, OnActivationModePropertyChanged));

/// <summary>
/// Gets or sets the activation mode that indicates whether the trigger should be applied.
/// </summary>
public CoreWindowActivationMode ActivationMode
{
get { return (CoreWindowActivationMode)GetValue(ActivationModeProperty); }
set { SetValue(ActivationModeProperty, value); }
}

private static void OnActivationModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is WindowActivationModeTrigger trigger)
{
trigger.UpdateTrigger();
}
}

/// <summary>
/// Initializes a new instance of the <see cref="WindowActivationModeTrigger"/> class,
/// and registers a handler for when the window completes activation or deactivation.
/// </summary>
public WindowActivationModeTrigger()
{
_coreWindow = Window.Current?.CoreWindow;
if (_coreWindow != null)
{
_coreWindow.Activated += CoreWindow_Activated;
Copy link
Preview

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event handler is subscribed in the constructor but never unsubscribed, which could lead to memory leaks. Consider implementing IDisposable or providing a cleanup mechanism to unsubscribe from the event when the trigger is no longer needed.

Copilot uses AI. Check for mistakes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been thinking about this and I don't think we can call Dispose on a CoreWindow or XAML trigger. Instead, we probably should cache the Activated event to prevent multiple calls.

}
Copy link
Preview

Copilot AI Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Window.Current property may not be available in multi-window scenarios or when called from background threads. Consider using a more robust method to obtain the CoreWindow reference or handle potential null scenarios more explicitly.

Suggested change
_coreWindow = Window.Current?.CoreWindow;
if (_coreWindow != null)
{
_coreWindow.Activated += CoreWindow_Activated;
}
_coreWindow = Window.Current?.CoreWindow ?? Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow;
if (_coreWindow != null)
{
_coreWindow.Activated += CoreWindow_Activated;
}
else
{
// Log or handle the case where no CoreWindow is available
System.Diagnostics.Debug.WriteLine("Warning: Unable to obtain a CoreWindow reference.");
}

Copilot uses AI. Check for mistakes.

}

private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs args)
{
UpdateTrigger();
}

private void UpdateTrigger()
{
if (_coreWindow != null)
{
SetActive(_coreWindow.ActivationMode == ActivationMode);
}
else
{
SetActive(false);
}
}
}
Loading