diff --git a/src/Files.App.CsWin32/Windows.Win32.Extras.cs b/src/Files.App.CsWin32/Windows.Win32.Extras.cs
index c12b9bc62569..e626c5390633 100644
--- a/src/Files.App.CsWin32/Windows.Win32.Extras.cs
+++ b/src/Files.App.CsWin32/Windows.Win32.Extras.cs
@@ -16,5 +16,51 @@ namespace UI.WindowsAndMessaging
{
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate LRESULT WNDPROC(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam);
+
+ /// Contains information about the size and position of a window.
+ ///
+ /// Learn more about this API from docs.microsoft.com.
+ ///
+ public partial struct WINDOWPOS
+ {
+ ///
+ /// Type: HWND A handle to the window.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal HWND hwnd;
+
+ ///
+ /// Type: HWND The position of the window in Z order (front-to-back position). This member can be a handle to the window behind which this window is placed, or can be one of the special values listed with the SetWindowPos function.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal HWND hwndInsertAfter;
+
+ ///
+ /// Type: int The position of the left edge of the window.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal int x;
+
+ ///
+ /// Type: int The position of the top edge of the window.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal int y;
+
+ ///
+ /// Type: int The window width, in pixels.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal int cx;
+
+ ///
+ /// Type: int The window height, in pixels.
+ /// Read more on docs.microsoft.com.
+ ///
+ internal int cy;
+
+ /// Type: UINT
+ public SET_WINDOW_POS_FLAGS flags;
+ }
}
}
diff --git a/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs b/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs
index ff57f45abc32..a93c4b707a9f 100644
--- a/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs
+++ b/src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs
@@ -4,6 +4,7 @@
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
using System.Reflection;
+using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.UI.WindowsAndMessaging;
@@ -58,5 +59,16 @@ public static void ChangeCursor(this UIElement uiElement, InputCursor cursor)
[cursor]
);
}
+
+ ///
+ /// Force window to stay at bottom of other upper windows.
+ ///
+ /// The lParam of the message.
+ public static void ForceWindowPosition(nint lParam)
+ {
+ var windowPos = Marshal.PtrToStructure(lParam);
+ windowPos.flags |= SET_WINDOW_POS_FLAGS.SWP_NOZORDER;
+ Marshal.StructureToPtr(windowPos, lParam, false);
+ }
}
}
diff --git a/src/Files.App/MainWindow.xaml.cs b/src/Files.App/MainWindow.xaml.cs
index e80e55247649..3f08fdfbb482 100644
--- a/src/Files.App/MainWindow.xaml.cs
+++ b/src/Files.App/MainWindow.xaml.cs
@@ -19,6 +19,8 @@ public sealed partial class MainWindow : WinUIEx.WindowEx
public static MainWindow Instance => _Instance ??= new();
public nint WindowHandle { get; }
+ private bool CanWindowToFront { get; set; } = true;
+ private readonly object _canWindowToFrontLock = new();
public MainWindow()
{
@@ -35,6 +37,8 @@ public MainWindow()
AppWindow.TitleBar.ButtonPressedBackgroundColor = Colors.Transparent;
AppWindow.TitleBar.ButtonHoverBackgroundColor = Colors.Transparent;
AppWindow.SetIcon(AppLifecycleHelper.AppIconPath);
+
+ WinUIEx.WindowManager.Get(this).WindowMessageReceived += WindowManager_WindowMessageReceived;
}
public void ShowSplashScreen()
@@ -339,5 +343,28 @@ x.tabItem.NavigationParameter.NavigationParameter is PaneNavigationArguments pan
}
}
}
+
+ public bool SetCanWindowToFront(bool canWindowToFront)
+ {
+ lock (_canWindowToFrontLock)
+ {
+ if (CanWindowToFront != canWindowToFront)
+ {
+ CanWindowToFront = canWindowToFront;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private const int WM_WINDOWPOSCHANGING = 0x0046;
+ private void WindowManager_WindowMessageReceived(object? sender, WinUIEx.Messaging.WindowMessageEventArgs e)
+ {
+ if ((!CanWindowToFront) && e.Message.MessageId == WM_WINDOWPOSCHANGING)
+ {
+ Win32Helper.ForceWindowPosition(e.Message.LParam);
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Files.App/Views/Layouts/BaseLayoutPage.cs b/src/Files.App/Views/Layouts/BaseLayoutPage.cs
index 4a78f5a34e13..0acc0707a171 100644
--- a/src/Files.App/Views/Layouts/BaseLayoutPage.cs
+++ b/src/Files.App/Views/Layouts/BaseLayoutPage.cs
@@ -66,6 +66,7 @@ public abstract class BaseLayoutPage : Page, IBaseLayoutPage, INotifyPropertyCha
private CancellationTokenSource? groupingCancellationToken;
private bool shiftPressed;
+ private bool itemDragging;
private ListedItem? dragOverItem = null;
private ListedItem? hoveredItem = null;
@@ -1012,6 +1013,9 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti
var storageItemList = orderedItems.Where(x => !(x.IsHiddenItem && x.IsLinkItem && x.IsRecycleBinItem && x.IsShortcut)).Select(x => VirtualStorageItem.FromListedItem(x));
e.Data.SetStorageItems(storageItemList, false);
}
+
+ MainWindow.Instance.SetCanWindowToFront(false);
+ itemDragging = true;
}
catch (Exception)
{
@@ -1019,6 +1023,13 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti
}
}
+ protected virtual void FileList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
+ {
+ itemDragging = false;
+ MainWindow.Instance.SetCanWindowToFront(true);
+ // No need to bring the window to the front
+ }
+
private void Item_DragLeave(object sender, DragEventArgs e)
{
var item = GetItemFromElement(sender);
@@ -1147,6 +1158,10 @@ protected void FileList_ContainerContentChanging(ListViewBase sender, ContainerC
{
RefreshContainer(args.ItemContainer, args.InRecycleQueue);
RefreshItem(args.ItemContainer, args.Item, args.InRecycleQueue, args);
+
+ itemDragging = false;
+ MainWindow.Instance.SetCanWindowToFront(true);
+ // No need to bring the window to the front
}
private void RefreshContainer(SelectorItem container, bool inRecycleQueue)
@@ -1154,6 +1169,8 @@ private void RefreshContainer(SelectorItem container, bool inRecycleQueue)
container.PointerPressed -= FileListItem_PointerPressed;
container.PointerEntered -= FileListItem_PointerEntered;
container.PointerExited -= FileListItem_PointerExited;
+ container.Tapped -= FileListItem_Tapped;
+ container.DoubleTapped -= FileListItem_DoubleTapped;
container.RightTapped -= FileListItem_RightTapped;
if (inRecycleQueue)
@@ -1163,12 +1180,11 @@ private void RefreshContainer(SelectorItem container, bool inRecycleQueue)
else
{
container.PointerPressed += FileListItem_PointerPressed;
+ container.PointerEntered += FileListItem_PointerEntered;
+ container.PointerExited += FileListItem_PointerExited;
+ container.Tapped += FileListItem_Tapped;
+ container.DoubleTapped += FileListItem_DoubleTapped;
container.RightTapped += FileListItem_RightTapped;
- if (UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
- {
- container.PointerEntered += FileListItem_PointerEntered;
- container.PointerExited += FileListItem_PointerExited;
- }
}
}
@@ -1200,6 +1216,10 @@ private void RefreshItem(SelectorItem container, object item, bool inRecycleQueu
protected internal void FileListItem_PointerPressed(object sender, PointerRoutedEventArgs e)
{
+ // Set can window to front and bring the window to the front if necessary
+ if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
+ Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
+
if (sender is not SelectorItem selectorItem)
return;
@@ -1225,6 +1245,10 @@ protected internal void FileListItem_PointerPressed(object sender, PointerRouted
protected internal void FileListItem_PointerEntered(object sender, PointerRoutedEventArgs e)
{
+ // Set can window to front before the item is dragged
+ if (sender is SelectorItem selectorItem && selectorItem.IsSelected)
+ MainWindow.Instance.SetCanWindowToFront(false);
+
if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
return;
@@ -1271,6 +1295,10 @@ selectedItems is not null &&
protected internal void FileListItem_PointerExited(object sender, PointerRoutedEventArgs e)
{
+ // Set can window to front
+ if (!itemDragging)
+ MainWindow.Instance.SetCanWindowToFront(true);
+
if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
return;
@@ -1278,8 +1306,26 @@ protected internal void FileListItem_PointerExited(object sender, PointerRoutedE
hoveredItem = null;
}
+ protected void FileListItem_Tapped(object sender, TappedRoutedEventArgs e)
+ {
+ // Set can window to front and bring the window to the front if necessary
+ if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
+ Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
+ }
+
+ protected void FileListItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
+ {
+ // Set can window to front and bring the window to the front if necessary
+ if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
+ Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
+ }
+
protected void FileListItem_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
+ // Set can window to front and bring the window to the front if necessary
+ if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
+ Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
+
var rightClickedItem = GetItemFromElement(sender);
if (rightClickedItem is not null && !((SelectorItem)sender).IsSelected)
diff --git a/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml b/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml
index aca14800c17f..b9d707b42174 100644
--- a/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml
+++ b/src/Files.App/Views/Layouts/ColumnLayoutPage.xaml
@@ -183,6 +183,7 @@
CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}"
ContainerContentChanging="FileList_ContainerContentChanging"
DoubleTapped="FileList_DoubleTapped"
+ DragItemsCompleted="FileList_DragItemsCompleted"
DragItemsStarting="FileList_DragItemsStarting"
DragOver="ItemsLayout_DragOver"
Drop="ItemsLayout_Drop"
diff --git a/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml b/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml
index 2c6c5d87bbdd..265bdf3e78a0 100644
--- a/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml
+++ b/src/Files.App/Views/Layouts/DetailsLayoutPage.xaml
@@ -229,6 +229,7 @@
CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}"
ContainerContentChanging="FileList_ContainerContentChanging"
DoubleTapped="FileList_DoubleTapped"
+ DragItemsCompleted="FileList_DragItemsCompleted"
DragItemsStarting="FileList_DragItemsStarting"
DragOver="ItemsLayout_DragOver"
Drop="ItemsLayout_Drop"
diff --git a/src/Files.App/Views/Layouts/GridLayoutPage.xaml b/src/Files.App/Views/Layouts/GridLayoutPage.xaml
index bb3ddef90e59..e4f61e8a851d 100644
--- a/src/Files.App/Views/Layouts/GridLayoutPage.xaml
+++ b/src/Files.App/Views/Layouts/GridLayoutPage.xaml
@@ -787,6 +787,7 @@
ContainerContentChanging="FileList_ContainerContentChanging"
DoubleTapped="FileList_DoubleTapped"
DragEnter="ItemsLayout_DragEnter"
+ DragItemsCompleted="FileList_DragItemsCompleted"
DragItemsStarting="FileList_DragItemsStarting"
DragLeave="ItemsLayout_DragLeave"
DragOver="ItemsLayout_DragOver"