From ab06e45488b94f2d767def229460283a50281e83 Mon Sep 17 00:00:00 2001 From: RMBGAME Date: Fri, 19 Nov 2021 20:08:06 +0800 Subject: [PATCH 1/3] TrayIcon should be re-added when the Explorer is restarted --- .../Interop/UnmanagedMethods.cs | 85 ++++++++++--------- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 12 ++- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 938f4222e0..9e514461d7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -903,9 +903,9 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); - + public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); - + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetDC(IntPtr hWnd); @@ -996,7 +996,7 @@ namespace Avalonia.Win32.Interop public static uint GetWindowLong(IntPtr hWnd, int nIndex) { - if(IntPtr.Size == 4) + if (IntPtr.Size == 4) { return GetWindowLong32b(hWnd, nIndex); } @@ -1023,7 +1023,7 @@ namespace Avalonia.Win32.Interop return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr((uint)value)).ToInt32(); } } - + public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle) { if (IntPtr.Size == 4) @@ -1057,14 +1057,14 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase); - - + + [DllImport("user32.dll")] public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect); [DllImport("user32.dll")] public static extern bool IsWindow(IntPtr hWnd); - + [DllImport("user32.dll")] public static extern bool IsWindowEnabled(IntPtr hWnd); @@ -1091,22 +1091,25 @@ namespace Avalonia.Win32.Interop [DllImport("user32")] public static extern IntPtr GetMessageExtraInfo(); - + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")] public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx); [DllImport("user32.dll")] public static extern void RegisterTouchWindow(IntPtr hWnd, int flags); - + [DllImport("user32.dll")] public static extern bool ReleaseCapture(); + [DllImport("user32.dll", SetLastError = true)] + public static extern uint RegisterWindowMessage(string lpString); + [DllImport("user32.dll")] public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetActiveWindow(); - + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr SetActiveWindow(IntPtr hWnd); @@ -1282,7 +1285,7 @@ namespace Avalonia.Win32.Interop [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibrary(string fileName); - + [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, int flags); @@ -1326,7 +1329,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags); - + [DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi); @@ -1334,14 +1337,14 @@ namespace Avalonia.Win32.Interop [DllImport("user32")] public static extern unsafe bool GetTouchInputInfo( IntPtr hTouchInput, - uint cInputs, + uint cInputs, TOUCHINPUT* pInputs, - int cbSize + int cbSize ); - + [DllImport("user32")] public static extern bool CloseTouchInputHandle(IntPtr hTouchInput); - + [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); @@ -1350,7 +1353,7 @@ namespace Avalonia.Win32.Interop public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); - + [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); @@ -1365,27 +1368,27 @@ namespace Avalonia.Win32.Interop [DllImport("gdi32.dll")] public static extern int ChoosePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); - + [DllImport("gdi32.dll")] public static extern int DescribePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); [DllImport("gdi32.dll")] public static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PixelFormatDescriptor pfd); - - + + [DllImport("gdi32.dll")] public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd); - + [DllImport("gdi32.dll")] public static extern bool SwapBuffers(IntPtr hdc); [DllImport("opengl32.dll")] public static extern IntPtr wglCreateContext(IntPtr hdc); - + [DllImport("opengl32.dll")] public static extern bool wglDeleteContext(IntPtr context); - + [DllImport("opengl32.dll")] public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context); @@ -1406,9 +1409,9 @@ namespace Avalonia.Win32.Interop uint dwMaximumSizeLow, string lpName); - [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); - + [DllImport("msvcrt.dll", EntryPoint = "memcpy", SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); @@ -1447,10 +1450,10 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern void DwmFlush(); - + [DllImport("dwmapi.dll")] public static extern bool DwmDefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult); - + [DllImport("dwmapi.dll")] public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); @@ -1507,8 +1510,8 @@ namespace Avalonia.Win32.Interop throw new Exception("RtlGetVersion failed!"); } } - - [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] + + [DllImport("kernel32", EntryPoint = "WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable); public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); @@ -1516,7 +1519,7 @@ namespace Avalonia.Win32.Interop internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable) { int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable); - if(result == WAIT_FAILED) + if (result == WAIT_FAILED) { throw new Win32Exception(); } @@ -1558,7 +1561,7 @@ namespace Avalonia.Win32.Interop DrawLeftBorder = 0x20, DrawTopBorder = 0x40, DrawRightBorder = 0x80, - DrawBottomBorder = 0x100, + DrawBottomBorder = 0x100, } [StructLayout(LayoutKind.Sequential)] @@ -1626,9 +1629,9 @@ namespace Avalonia.Win32.Interop MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI - } + } - public enum ClipboardFormat + public enum ClipboardFormat { /// /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text. @@ -1679,7 +1682,7 @@ namespace Avalonia.Win32.Interop public int X; public int Y; } - + public struct SIZE { public int X; @@ -1880,7 +1883,7 @@ namespace Avalonia.Win32.Interop OFN_NOREADONLYRETURN = 0x00008000, OFN_OVERWRITEPROMPT = 0x00000002 } - + public enum HRESULT : uint { S_FALSE = 0x0001, @@ -2198,13 +2201,13 @@ namespace Avalonia.Win32.Interop internal interface IDropTarget { [PreserveSig] - UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)][In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); [PreserveSig] - UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragLeave(); [PreserveSig] - UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)][In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); } [ComImport] @@ -2213,9 +2216,9 @@ namespace Avalonia.Win32.Interop internal interface IDropSource { [PreserveSig] - int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState); + int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState); [PreserveSig] - int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect); + int GiveFeedback([MarshalAs(UnmanagedType.U4)][In] int dwEffect); } diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 23395dd9b5..c28ec94fe8 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -24,6 +24,7 @@ namespace Avalonia.Win32 private readonly Win32NativeToManagedMenuExporter _exporter; private static readonly Dictionary s_trayIcons = new Dictionary(); private bool _disposedValue; + private static readonly uint WM_TASKBARCREATED = UnmanagedMethods.RegisterWindowMessage("TaskbarCreated"); public TrayIconImpl() { @@ -44,6 +45,15 @@ namespace Avalonia.Win32 { s_trayIcons[wParam.ToInt32()].WndProc(hWnd, msg, wParam, lParam); } + + if (msg == WM_TASKBARCREATED) + { + foreach (var tray in s_trayIcons.Values) + { + tray.UpdateIcon(true); + tray.UpdateIcon(); + } + } } public void SetIcon(IWindowIconImpl? icon) @@ -145,7 +155,7 @@ namespace Avalonia.Win32 private enum CustomWindowsMessage : uint { WM_TRAYICON = WindowsMessage.WM_APP + 1024, - WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024 + WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024, } private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter, IStyleable From 6bb6c0e5c92716521aac4915904f509450966397 Mon Sep 17 00:00:00 2001 From: RMBGAME Date: Fri, 19 Nov 2021 21:55:35 +0800 Subject: [PATCH 2/3] Only re-add visible TrayIcon --- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index c28ec94fe8..86732539f1 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -50,8 +50,11 @@ namespace Avalonia.Win32 { foreach (var tray in s_trayIcons.Values) { - tray.UpdateIcon(true); - tray.UpdateIcon(); + if (tray._iconAdded) + { + tray.UpdateIcon(true); + tray.UpdateIcon(); + } } } } From 7c63e1a60b9b20ee5bec85c26a21a15f801c1600 Mon Sep 17 00:00:00 2001 From: Tako Date: Mon, 25 Apr 2022 17:42:52 +0300 Subject: [PATCH 3/3] Fix ContextMenu freeze. --- src/Avalonia.Controls/ItemsControl.cs | 2 +- .../Platform/DefaultMenuInteractionHandler.cs | 2 +- .../Primitives/SelectingItemsControlTests.cs | 45 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 256160a116..56b0014c05 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -518,7 +518,7 @@ namespace Avalonia.Controls } c = result; - } while (c != null && c != from); + } while (c != null && c != from && direction != NavigationDirection.First && direction != NavigationDirection.Last); return null; } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 6e9ac537f1..2f9bf0ac06 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -417,7 +417,7 @@ namespace Avalonia.Controls.Platform protected internal virtual void MenuOpened(object? sender, RoutedEventArgs e) { - if (e.Source == Menu) + if (e.Source is Menu) { Menu?.MoveSelection(NavigationDirection.First, true); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 0e0ca7cd25..3d36395c3a 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -6,6 +6,7 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; +using System.Threading.Tasks; using Avalonia.Collections; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -1614,6 +1615,50 @@ namespace Avalonia.Controls.UnitTests.Primitives target.MoveSelection(NavigationDirection.Next, true); } + [Fact(Timeout = 2000)] + public async Task MoveSelection_Does_Not_Hang_With_No_Focusable_Controls_And_Moving_Selection_To_The_First_Item() + { + var target = new TestSelector + { + Template = Template(), + Items = new[] + { + new ListBoxItem { Focusable = false }, + new ListBoxItem(), + } + }; + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + // Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below. + // https://github.com/xunit/xunit/issues/2222 + await Task.Run(() => target.MoveSelection(NavigationDirection.First, true)); + Assert.Equal(-1, target.SelectedIndex); + } + + [Fact(Timeout = 2000)] + public async Task MoveSelection_Does_Not_Hang_With_No_Focusable_Controls_And_Moving_Selection_To_The_Last_Item() + { + var target = new TestSelector + { + Template = Template(), + Items = new[] + { + new ListBoxItem(), + new ListBoxItem { Focusable = false }, + } + }; + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + // Timeout in xUnit doesen't work with synchronous methods so we need to apply hack below. + // https://github.com/xunit/xunit/issues/2222 + await Task.Run(() => target.MoveSelection(NavigationDirection.Last, true)); + Assert.Equal(-1, target.SelectedIndex); + } + [Fact] public void MoveSelection_Does_Select_Disabled_Controls() {