Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public enum DwmWindowAttribute
TEXT_COLOR,
VISIBLE_FRAME_BORDER_THICKNESS,
SYSTEMBACKDROP_TYPE,
SYSTEMBACKDROP_ALWAYS_ACTIVE,
LAST
}

Expand Down
112 changes: 112 additions & 0 deletions EleCho.WpfSuite.Helpers/Helpers/WindowOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,40 @@ public static void SetBackdrop(DependencyObject obj, WindowBackdrop value)
}


/// <summary>
/// Get value of IsBackdropAlwaysActive property
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
/// <remarks>
/// This property controls whether the window backdrop (Mica/Acrylic) remains active when the window loses focus.
/// By default (false), Windows automatically deactivates the backdrop when the window is not focused.
/// When set to true, the backdrop will remain active even when the window loses focus.
/// This property is primarily designed for Window objects, but also supports Popup, ToolTip, ContextMenu, and MenuItem for consistency with other WindowOption properties.
/// </remarks>
[AttachedPropertyBrowsableForType(typeof(Window))]
public static bool GetIsBackdropAlwaysActive(DependencyObject obj)
{
return (bool)obj.GetValue(IsBackdropAlwaysActiveProperty);
}

/// <summary>
/// Set value of IsBackdropAlwaysActive property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
/// <remarks>
/// This property controls whether the window backdrop (Mica/Acrylic) remains active when the window loses focus.
/// By default (false), Windows automatically deactivates the backdrop when the window is not focused.
/// When set to true, the backdrop will remain active even when the window loses focus.
/// This property is primarily designed for Window objects, but also supports Popup, ToolTip, ContextMenu, and MenuItem for consistency with other WindowOption properties.
/// </remarks>
public static void SetIsBackdropAlwaysActive(DependencyObject obj, bool value)
{
obj.SetValue(IsBackdropAlwaysActiveProperty, value);
}


/// <summary>
/// Get value of Corner property
/// </summary>
Expand Down Expand Up @@ -398,6 +432,18 @@ public static void SetIsCaption(DependencyObject obj, bool value)
public static readonly DependencyProperty BackdropProperty =
DependencyProperty.RegisterAttached("Backdrop", typeof(WindowBackdrop), typeof(WindowOption), new FrameworkPropertyMetadata(WindowBackdrop.Auto, OnBackdropChanged));

/// <summary>
/// The DependencyProperty of IsBackdropAlwaysActive property
/// </summary>
/// <remarks>
/// Controls whether the window backdrop (Mica/Acrylic) remains active when the window loses focus.
/// Default value is false, meaning the backdrop will be deactivated when the window loses focus (standard Windows behavior).
/// When set to true, the backdrop will remain active even when the window is not focused.
/// This property requires Windows 11 Build 22621 or later.
/// </remarks>
public static readonly DependencyProperty IsBackdropAlwaysActiveProperty =
DependencyProperty.RegisterAttached("IsBackdropAlwaysActive", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsBackdropAlwaysActiveChanged));

/// <summary>
/// The DependencyProperty of Corner property
/// </summary>
Expand Down Expand Up @@ -1010,6 +1056,54 @@ backdrop is not WindowBackdrop.Auto &&
}
}

private static void OnIsBackdropAlwaysActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var isAlwaysActive = GetIsBackdropAlwaysActive(d);

if (GetWindowHwndSource(d) is HwndSource hwndSource)
{
ApplyIsBackdropAlwaysActive(hwndSource, isAlwaysActive);
}

// Always remove the event handler first to prevent duplicates
if (d is Window window)
{
window.SourceInitialized -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
window.SourceInitialized += EventHandlerApplyIsBackdropAlwaysActive;
}
else if (d is Popup popup)
{
popup.Opened -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
popup.Opened += EventHandlerApplyIsBackdropAlwaysActive;
}
else if (d is ToolTip toolTip)
{
toolTip.Opened -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
toolTip.Opened += EventHandlerApplyIsBackdropAlwaysActive;
}
else if (d is ContextMenu contextMenu)
{
contextMenu.Opened -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
contextMenu.Opened += EventHandlerApplyIsBackdropAlwaysActive;
}
else if (d is MenuItem menuItem)
{
menuItem.SubmenuOpened -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
menuItem.SubmenuOpened += EventHandlerApplyIsBackdropAlwaysActive;
}
else if (d is FrameworkElement element)
{
element.Loaded -= EventHandlerApplyIsBackdropAlwaysActive;
if (isAlwaysActive)
element.Loaded += EventHandlerApplyIsBackdropAlwaysActive;
}
}

private static void OnCornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var corner = GetCorner(d);
Expand Down Expand Up @@ -1988,6 +2082,17 @@ private static unsafe void ApplyCorner(HwndSource hwndSource, WindowCorner corne
DwmSetWindowAttribute(handle, DwmWindowAttribute.WINDOW_CORNER_PREFERENCE, (nint)(void*)&corner, (uint)sizeof(WindowCorner));
}

private static unsafe void ApplyIsBackdropAlwaysActive(HwndSource hwndSource, bool isAlwaysActive)
{
// this api is only available on windows 11 22621
if (s_versionCurrentWindows < s_versionWindows11_22621)
return;

var handle = hwndSource.Handle;

DwmSetWindowAttribute(handle, DwmWindowAttribute.SYSTEMBACKDROP_ALWAYS_ACTIVE, (nint)(void*)&isAlwaysActive, (uint)sizeof(bool));
}

private static unsafe void ApplyCaptionColor(HwndSource hwndSource, WindowOptionColor color)
{
// this api is only available on windows 11 22000
Expand Down Expand Up @@ -2423,6 +2528,13 @@ private static void EventHandlerApplyCorner(object? sender, EventArgs e)
ApplyCorner(hwndSource, GetCorner(d));
}

private static void EventHandlerApplyIsBackdropAlwaysActive(object? sender, EventArgs e)
{
if (sender is DependencyObject d &&
GetWindowHwndSource(d) is HwndSource hwndSource)
ApplyIsBackdropAlwaysActive(hwndSource, GetIsBackdropAlwaysActive(d));
}

private static void EventHandlerApplyCaptionColor(object? sender, EventArgs e)
{
if (sender is DependencyObject d &&
Expand Down
69 changes: 69 additions & 0 deletions WpfTest/Tests/BackdropAlwaysActiveTestWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<Window x:Class="WpfTest.Tests.BackdropAlwaysActiveTestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest.Tests"
xmlns:ws="https://schemas.elecho.dev/wpfsuite"
mc:Ignorable="d"
Title="Backdrop Always Active Test" Height="400" Width="600"
Background="Transparent"
d:Background="White"
ws:WindowOption.Backdrop="Acrylic"
ws:WindowOption.IsBackdropAlwaysActive="True">

<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="45"
GlassFrameThickness="-1"/>
</WindowChrome.WindowChrome>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<!-- Title Bar -->
<Grid Grid.Row="0" Height="45" Background="#20000000">
<TextBlock Text="Backdrop Always Active Test"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="15,0,0,0"
FontSize="14"
FontWeight="SemiBold"/>
</Grid>

<!-- Content -->
<Grid Grid.Row="1" Margin="20">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Test Instructions:"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,15"/>

<TextBlock TextWrapping="Wrap"
FontSize="14"
MaxWidth="500"
Margin="0,0,0,10">
<Run Text="1. This window has"/>
<Run Text="ws:WindowOption.IsBackdropAlwaysActive=&quot;True&quot;" FontWeight="Bold"/>
<LineBreak/>
<Run Text="2. Click another window to make this window lose focus"/>
<LineBreak/>
<Run Text="3. The Acrylic backdrop should"/>
<Run Text="remain active" FontWeight="Bold"/>
<Run Text="even when the window loses focus"/>
<LineBreak/>
<LineBreak/>
<Run Text="Compare this with the standard behavior:"/>
</TextBlock>

<Button Content="Open Window Without Always Active"
Click="OpenNormalWindow_Click"
Padding="15,8"
Margin="0,15,0,0"
HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Grid>
</Window>
21 changes: 21 additions & 0 deletions WpfTest/Tests/BackdropAlwaysActiveTestWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Windows;

namespace WpfTest.Tests
{
/// <summary>
/// Interaction logic for BackdropAlwaysActiveTestWindow.xaml
/// </summary>
public partial class BackdropAlwaysActiveTestWindow : Window
{
public BackdropAlwaysActiveTestWindow()
{
InitializeComponent();
}

private void OpenNormalWindow_Click(object sender, RoutedEventArgs e)
{
var normalWindow = new BackdropNormalTestWindow();
normalWindow.Show();
}
}
}
59 changes: 59 additions & 0 deletions WpfTest/Tests/BackdropNormalTestWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<Window x:Class="WpfTest.Tests.BackdropNormalTestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest.Tests"
xmlns:ws="https://schemas.elecho.dev/wpfsuite"
mc:Ignorable="d"
Title="Normal Backdrop Test" Height="350" Width="500"
Background="Transparent"
d:Background="White"
ws:WindowOption.Backdrop="Acrylic">

<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="45"
GlassFrameThickness="-1"/>
</WindowChrome.WindowChrome>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<!-- Title Bar -->
<Grid Grid.Row="0" Height="45" Background="#20000000">
<TextBlock Text="Normal Backdrop Test"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="15,0,0,0"
FontSize="14"
FontWeight="SemiBold"/>
</Grid>

<!-- Content -->
<Grid Grid.Row="1" Margin="20">
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock Text="Normal Behavior Window"
FontSize="18"
FontWeight="Bold"
Margin="0,0,0,15"/>

<TextBlock TextWrapping="Wrap"
FontSize="14"
MaxWidth="450"
TextAlignment="Center">
<Run Text="This window has"/>
<Run Text="ws:WindowOption.IsBackdropAlwaysActive=&quot;False&quot;" FontWeight="Bold"/>
<Run Text="(default behavior)"/>
<LineBreak/>
<LineBreak/>
<Run Text="When this window loses focus, the Acrylic backdrop"/>
<LineBreak/>
<Run Text="will become inactive and turn gray-white."/>
</TextBlock>
</StackPanel>
</Grid>
</Grid>
</Window>
15 changes: 15 additions & 0 deletions WpfTest/Tests/BackdropNormalTestWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Windows;

namespace WpfTest.Tests
{
/// <summary>
/// Interaction logic for BackdropNormalTestWindow.xaml
/// </summary>
public partial class BackdropNormalTestWindow : Window
{
public BackdropNormalTestWindow()
{
InitializeComponent();
}
}
}
9 changes: 9 additions & 0 deletions WpfTest/Tests/WindowCompositionTest.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@
<ws:Button Content="Custom Acrylic Window" Command="{Binding OpenDarkCustomAcrylicWindowCommand}"/>
</ws:StackPanel>
</ws:GroupBox>

<ws:GroupBox Header="Backdrop Always Active"
Padding="8">
<ws:StackPanel Orientation="Horizontal"
HorizontalAlignment="Left"
Spacing="8">
<ws:Button Content="Test Backdrop Always Active" Command="{Binding OpenBackdropAlwaysActiveTestWindowCommand}"/>
</ws:StackPanel>
</ws:GroupBox>
</ws:StackPanel>
</Grid>
</ws:ScrollViewer>
Expand Down
6 changes: 6 additions & 0 deletions WpfTest/Tests/WindowCompositionTest.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,11 @@ public void OpenDarkCustomAcrylicWindow()
window.Foreground = new SolidColorBrush(Color.FromRgb(214, 214, 214));
window.Show();
}

[RelayCommand]
public void OpenBackdropAlwaysActiveTestWindow()
{
new BackdropAlwaysActiveTestWindow().Show();
}
}
}