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/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9d53691597..b1e4d8ca01 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -914,9 +914,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); @@ -1007,7 +1007,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); } @@ -1034,7 +1034,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) @@ -1068,14 +1068,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); @@ -1102,22 +1102,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); @@ -1304,7 +1307,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); @@ -1348,7 +1351,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); @@ -1356,14 +1359,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); @@ -1372,7 +1375,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); @@ -1387,27 +1390,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); @@ -1428,9 +1431,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, IntPtr target); @@ -1472,10 +1475,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); @@ -1542,8 +1545,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); @@ -1551,7 +1554,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(); } @@ -1699,7 +1702,7 @@ namespace Avalonia.Win32.Interop DrawLeftBorder = 0x20, DrawTopBorder = 0x40, DrawRightBorder = 0x80, - DrawBottomBorder = 0x100, + DrawBottomBorder = 0x100, } [StructLayout(LayoutKind.Sequential)] @@ -1767,9 +1770,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. @@ -1820,7 +1823,7 @@ namespace Avalonia.Win32.Interop public int X; public int Y; } - + public struct SIZE { public int X; @@ -2021,7 +2024,7 @@ namespace Avalonia.Win32.Interop OFN_NOREADONLYRETURN = 0x00008000, OFN_OVERWRITEPROMPT = 0x00000002 } - + public enum HRESULT : uint { S_FALSE = 0x0001, diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 6484ae6c54..771ab232da 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,18 @@ namespace Avalonia.Win32 { s_trayIcons[wParam.ToInt32()].WndProc(hWnd, msg, wParam, lParam); } + + if (msg == WM_TASKBARCREATED) + { + foreach (var tray in s_trayIcons.Values) + { + if (tray._iconAdded) + { + tray.UpdateIcon(true); + tray.UpdateIcon(); + } + } + } } public void SetIcon(IWindowIconImpl? icon) @@ -145,7 +158,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 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() {