Skip to content

Commit 29a3ce5

Browse files
authored
Feature: Improved behavior when dragging items from an unfocused Files window (#16508)
1 parent 973a3f8 commit 29a3ce5

File tree

7 files changed

+139
-5
lines changed

7 files changed

+139
-5
lines changed

src/Files.App.CsWin32/Windows.Win32.Extras.cs

+46
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,51 @@ namespace UI.WindowsAndMessaging
1616
{
1717
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
1818
public delegate LRESULT WNDPROC(HWND hWnd, uint msg, WPARAM wParam, LPARAM lParam);
19+
20+
/// <summary>Contains information about the size and position of a window.</summary>
21+
/// <remarks>
22+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos">Learn more about this API from docs.microsoft.com</see>.</para>
23+
/// </remarks>
24+
public partial struct WINDOWPOS
25+
{
26+
/// <summary>
27+
/// <para>Type: <b>HWND</b> A handle to the window.</para>
28+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
29+
/// </summary>
30+
internal HWND hwnd;
31+
32+
/// <summary>
33+
/// <para>Type: <b>HWND</b> 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 <a href="https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-setwindowpos">SetWindowPos</a> function.</para>
34+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
35+
/// </summary>
36+
internal HWND hwndInsertAfter;
37+
38+
/// <summary>
39+
/// <para>Type: <b>int</b> The position of the left edge of the window.</para>
40+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
41+
/// </summary>
42+
internal int x;
43+
44+
/// <summary>
45+
/// <para>Type: <b>int</b> The position of the top edge of the window.</para>
46+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
47+
/// </summary>
48+
internal int y;
49+
50+
/// <summary>
51+
/// <para>Type: <b>int</b> The window width, in pixels.</para>
52+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
53+
/// </summary>
54+
internal int cx;
55+
56+
/// <summary>
57+
/// <para>Type: <b>int</b> The window height, in pixels.</para>
58+
/// <para><see href="https://learn.microsoft.com/windows/win32/api/winuser/ns-winuser-windowpos#members">Read more on docs.microsoft.com</see>.</para>
59+
/// </summary>
60+
internal int cy;
61+
62+
/// <summary>Type: <b>UINT</b></summary>
63+
public SET_WINDOW_POS_FLAGS flags;
64+
}
1965
}
2066
}

src/Files.App/Helpers/Win32/Win32Helper.WindowManagement.cs

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.UI.Input;
55
using Microsoft.UI.Xaml;
66
using System.Reflection;
7+
using System.Runtime.InteropServices;
78
using Windows.Win32;
89
using Windows.Win32.UI.WindowsAndMessaging;
910

@@ -58,5 +59,16 @@ public static void ChangeCursor(this UIElement uiElement, InputCursor cursor)
5859
[cursor]
5960
);
6061
}
62+
63+
/// <summary>
64+
/// Force window to stay at bottom of other upper windows.
65+
/// </summary>
66+
/// <param name="lParam">The lParam of the message.</param>
67+
public static void ForceWindowPosition(nint lParam)
68+
{
69+
var windowPos = Marshal.PtrToStructure<WINDOWPOS>(lParam);
70+
windowPos.flags |= SET_WINDOW_POS_FLAGS.SWP_NOZORDER;
71+
Marshal.StructureToPtr(windowPos, lParam, false);
72+
}
6173
}
6274
}

src/Files.App/MainWindow.xaml.cs

+27
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public sealed partial class MainWindow : WinUIEx.WindowEx
1919
public static MainWindow Instance => _Instance ??= new();
2020

2121
public nint WindowHandle { get; }
22+
private bool CanWindowToFront { get; set; } = true;
23+
private readonly object _canWindowToFrontLock = new();
2224

2325
public MainWindow()
2426
{
@@ -35,6 +37,8 @@ public MainWindow()
3537
AppWindow.TitleBar.ButtonPressedBackgroundColor = Colors.Transparent;
3638
AppWindow.TitleBar.ButtonHoverBackgroundColor = Colors.Transparent;
3739
AppWindow.SetIcon(AppLifecycleHelper.AppIconPath);
40+
41+
WinUIEx.WindowManager.Get(this).WindowMessageReceived += WindowManager_WindowMessageReceived;
3842
}
3943

4044
public void ShowSplashScreen()
@@ -339,5 +343,28 @@ x.tabItem.NavigationParameter.NavigationParameter is PaneNavigationArguments pan
339343
}
340344
}
341345
}
346+
347+
public bool SetCanWindowToFront(bool canWindowToFront)
348+
{
349+
lock (_canWindowToFrontLock)
350+
{
351+
if (CanWindowToFront != canWindowToFront)
352+
{
353+
CanWindowToFront = canWindowToFront;
354+
return true;
355+
}
356+
return false;
357+
}
358+
}
359+
360+
private const int WM_WINDOWPOSCHANGING = 0x0046;
361+
private void WindowManager_WindowMessageReceived(object? sender, WinUIEx.Messaging.WindowMessageEventArgs e)
362+
{
363+
if ((!CanWindowToFront) && e.Message.MessageId == WM_WINDOWPOSCHANGING)
364+
{
365+
Win32Helper.ForceWindowPosition(e.Message.LParam);
366+
e.Handled = true;
367+
}
368+
}
342369
}
343370
}

src/Files.App/Views/Layouts/BaseLayoutPage.cs

+51-5
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public abstract class BaseLayoutPage : Page, IBaseLayoutPage, INotifyPropertyCha
6666
private CancellationTokenSource? groupingCancellationToken;
6767

6868
private bool shiftPressed;
69+
private bool itemDragging;
6970

7071
private ListedItem? dragOverItem = null;
7172
private ListedItem? hoveredItem = null;
@@ -1012,13 +1013,23 @@ protected virtual void FileList_DragItemsStarting(object sender, DragItemsStarti
10121013
var storageItemList = orderedItems.Where(x => !(x.IsHiddenItem && x.IsLinkItem && x.IsRecycleBinItem && x.IsShortcut)).Select(x => VirtualStorageItem.FromListedItem(x));
10131014
e.Data.SetStorageItems(storageItemList, false);
10141015
}
1016+
1017+
MainWindow.Instance.SetCanWindowToFront(false);
1018+
itemDragging = true;
10151019
}
10161020
catch (Exception)
10171021
{
10181022
e.Cancel = true;
10191023
}
10201024
}
10211025

1026+
protected virtual void FileList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
1027+
{
1028+
itemDragging = false;
1029+
MainWindow.Instance.SetCanWindowToFront(true);
1030+
// No need to bring the window to the front
1031+
}
1032+
10221033
private void Item_DragLeave(object sender, DragEventArgs e)
10231034
{
10241035
var item = GetItemFromElement(sender);
@@ -1147,13 +1158,19 @@ protected void FileList_ContainerContentChanging(ListViewBase sender, ContainerC
11471158
{
11481159
RefreshContainer(args.ItemContainer, args.InRecycleQueue);
11491160
RefreshItem(args.ItemContainer, args.Item, args.InRecycleQueue, args);
1161+
1162+
itemDragging = false;
1163+
MainWindow.Instance.SetCanWindowToFront(true);
1164+
// No need to bring the window to the front
11501165
}
11511166

11521167
private void RefreshContainer(SelectorItem container, bool inRecycleQueue)
11531168
{
11541169
container.PointerPressed -= FileListItem_PointerPressed;
11551170
container.PointerEntered -= FileListItem_PointerEntered;
11561171
container.PointerExited -= FileListItem_PointerExited;
1172+
container.Tapped -= FileListItem_Tapped;
1173+
container.DoubleTapped -= FileListItem_DoubleTapped;
11571174
container.RightTapped -= FileListItem_RightTapped;
11581175

11591176
if (inRecycleQueue)
@@ -1163,12 +1180,11 @@ private void RefreshContainer(SelectorItem container, bool inRecycleQueue)
11631180
else
11641181
{
11651182
container.PointerPressed += FileListItem_PointerPressed;
1183+
container.PointerEntered += FileListItem_PointerEntered;
1184+
container.PointerExited += FileListItem_PointerExited;
1185+
container.Tapped += FileListItem_Tapped;
1186+
container.DoubleTapped += FileListItem_DoubleTapped;
11661187
container.RightTapped += FileListItem_RightTapped;
1167-
if (UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
1168-
{
1169-
container.PointerEntered += FileListItem_PointerEntered;
1170-
container.PointerExited += FileListItem_PointerExited;
1171-
}
11721188
}
11731189
}
11741190

@@ -1200,6 +1216,10 @@ private void RefreshItem(SelectorItem container, object item, bool inRecycleQueu
12001216

12011217
protected internal void FileListItem_PointerPressed(object sender, PointerRoutedEventArgs e)
12021218
{
1219+
// Set can window to front and bring the window to the front if necessary
1220+
if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
1221+
Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
1222+
12031223
if (sender is not SelectorItem selectorItem)
12041224
return;
12051225

@@ -1225,6 +1245,10 @@ protected internal void FileListItem_PointerPressed(object sender, PointerRouted
12251245

12261246
protected internal void FileListItem_PointerEntered(object sender, PointerRoutedEventArgs e)
12271247
{
1248+
// Set can window to front before the item is dragged
1249+
if (sender is SelectorItem selectorItem && selectorItem.IsSelected)
1250+
MainWindow.Instance.SetCanWindowToFront(false);
1251+
12281252
if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
12291253
return;
12301254

@@ -1271,15 +1295,37 @@ selectedItems is not null &&
12711295

12721296
protected internal void FileListItem_PointerExited(object sender, PointerRoutedEventArgs e)
12731297
{
1298+
// Set can window to front
1299+
if (!itemDragging)
1300+
MainWindow.Instance.SetCanWindowToFront(true);
1301+
12741302
if (!UserSettingsService.FoldersSettingsService.SelectFilesOnHover)
12751303
return;
12761304

12771305
hoverTimer.Stop();
12781306
hoveredItem = null;
12791307
}
12801308

1309+
protected void FileListItem_Tapped(object sender, TappedRoutedEventArgs e)
1310+
{
1311+
// Set can window to front and bring the window to the front if necessary
1312+
if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
1313+
Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
1314+
}
1315+
1316+
protected void FileListItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
1317+
{
1318+
// Set can window to front and bring the window to the front if necessary
1319+
if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
1320+
Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
1321+
}
1322+
12811323
protected void FileListItem_RightTapped(object sender, RightTappedRoutedEventArgs e)
12821324
{
1325+
// Set can window to front and bring the window to the front if necessary
1326+
if ((!itemDragging) && MainWindow.Instance.SetCanWindowToFront(true))
1327+
Win32Helper.BringToForegroundEx(new(MainWindow.Instance.WindowHandle));
1328+
12831329
var rightClickedItem = GetItemFromElement(sender);
12841330

12851331
if (rightClickedItem is not null && !((SelectorItem)sender).IsSelected)

src/Files.App/Views/Layouts/ColumnLayoutPage.xaml

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}"
184184
ContainerContentChanging="FileList_ContainerContentChanging"
185185
DoubleTapped="FileList_DoubleTapped"
186+
DragItemsCompleted="FileList_DragItemsCompleted"
186187
DragItemsStarting="FileList_DragItemsStarting"
187188
DragOver="ItemsLayout_DragOver"
188189
Drop="ItemsLayout_Drop"

src/Files.App/Views/Layouts/DetailsLayoutPage.xaml

+1
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
CanDragItems="{x:Bind AllowItemDrag, Mode=OneWay}"
230230
ContainerContentChanging="FileList_ContainerContentChanging"
231231
DoubleTapped="FileList_DoubleTapped"
232+
DragItemsCompleted="FileList_DragItemsCompleted"
232233
DragItemsStarting="FileList_DragItemsStarting"
233234
DragOver="ItemsLayout_DragOver"
234235
Drop="ItemsLayout_Drop"

src/Files.App/Views/Layouts/GridLayoutPage.xaml

+1
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@
787787
ContainerContentChanging="FileList_ContainerContentChanging"
788788
DoubleTapped="FileList_DoubleTapped"
789789
DragEnter="ItemsLayout_DragEnter"
790+
DragItemsCompleted="FileList_DragItemsCompleted"
790791
DragItemsStarting="FileList_DragItemsStarting"
791792
DragLeave="ItemsLayout_DragLeave"
792793
DragOver="ItemsLayout_DragOver"

0 commit comments

Comments
 (0)