From cb4c912ab98998eeec3ed46e17ddb5e34a3146bf Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Tue, 13 Oct 2026 13:08:23 -0600 Subject: [PATCH 1/2] =?UTF-8?q?fix(systemtray):=20deduplicar=20eco=20NIN.S?= =?UTF-8?q?ELECT=20tras=20WM=5FLBUTTONUP=20=E2=80=94=20#58?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Core/SystemTray/SystemTrayBroker.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/EasyWindowsApplication/Core/SystemTray/SystemTrayBroker.cs b/src/EasyWindowsApplication/Core/SystemTray/SystemTrayBroker.cs index cfdc535..a7e5433 100644 --- a/src/EasyWindowsApplication/Core/SystemTray/SystemTrayBroker.cs +++ b/src/EasyWindowsApplication/Core/SystemTray/SystemTrayBroker.cs @@ -26,6 +26,7 @@ internal sealed class SystemTrayBroker private bool _disposed; private int _lastHoverTick; + private int _lastMainTapUpTick; // tick del último WM_LBUTTONUP consumido como MainTap private bool _holding; private readonly TapChainCounter _counter = new(); @@ -135,7 +136,13 @@ private nint OnCallback(nint wParam, nint lParam) case WM_RBUTTONUP: if (IsCursorOverIcon()) Fire(typeof(AlternativeTap1)); break; case WM_MBUTTONUP: if (IsCursorOverIcon()) Fire(typeof(AlternativeTap2)); break; case WM.CONTEXTMENU: OnContextMenuKey(); break; - case var m when m == NIN.SELECT || m == NIN.KEYSELECT: Fire(typeof(MainTap)); break; + // NIN.SELECT/KEYSELECT: en VERSION_4 Explorer lo envía junto a WM_LBUTTONUP + // en cada click de ratón. Deduplicar (issue #58): si hay un MainTap de ratón + // reciente dentro del tiempo de doble click, este mensaje es su eco. + case var m when m == NIN.SELECT || m == NIN.KEYSELECT: + if (unchecked(Environment.TickCount - _lastMainTapUpTick) > (int)Win32.GetDoubleClickTime()) + Fire(typeof(MainTap)); + break; case var m when m == NIN.POPUPOPEN: OnHover(); break; } return 0; @@ -213,6 +220,7 @@ private void OnButtonUp() } else { + _lastMainTapUpTick = Environment.TickCount; Fire(typeof(MainTap)); } } From fd1ee1ed5979cca3244f709945049aeaa9652fea Mon Sep 17 00:00:00 2001 From: Raul Avila Catala Date: Tue, 13 Oct 2026 13:15:41 -0600 Subject: [PATCH 2/2] ajustes para abordar el issue #60 --- src/EasyWindowsApplication/Share/IMenu.cs | 2 - src/Sample/Program.cs | 154 ++-------------------- 2 files changed, 13 insertions(+), 143 deletions(-) diff --git a/src/EasyWindowsApplication/Share/IMenu.cs b/src/EasyWindowsApplication/Share/IMenu.cs index e50d71c..f4b9112 100644 --- a/src/EasyWindowsApplication/Share/IMenu.cs +++ b/src/EasyWindowsApplication/Share/IMenu.cs @@ -3,8 +3,6 @@ namespace EasyWindowsApplication.Share; public interface IMenu : IViewSurface { void Show(); - void ShowAtCursor(); - void ShowAtIcon(); void Hide(); void Close(); } diff --git a/src/Sample/Program.cs b/src/Sample/Program.cs index f5948d6..94272e0 100644 --- a/src/Sample/Program.cs +++ b/src/Sample/Program.cs @@ -13,162 +13,34 @@ ly.Window(iw => iw .Name("MainWindow") .Title("EWA - Demo") - .Dimensions(525, 494) - .Content(c => c - .Spacing(8) - .Padding(8) - .Children(ch => - { - ch.View(lb => lb.Name("LbResult").Text("Press a button to test an AlternativeWindow.")); - ch.View(b => b.Name("BtnAltWindow").Text("Secondary window(HWND)")); - ch.View(b => b.Name("BtnMenu").Text("Context menu (IMenu)")); - ch.View(b => b.Name("BtnTaskDialog").Text("TaskDialog — .Content() mode")); - ch.View(b => b.Name("BtnTaskDialogMin").Text("TaskDialog — .Text() mode")); - ch.View(b => b.Name("BtnOpenFile").Text("OpenFileDialog")); - ch.View(b => b.Name("BtnSaveFile").Text("SaveFileDialog")); - ch.View(b => b.Name("BtnSelectFolder").Text("SelectFolderDialog")); - ch.View(b => b.Name("BtnWinState").Text("Window: Min/Max/Restore/Fullscreen")); - ch.View(b => b.Name("BtnZOrder").Text("BringToFront + SendToBack demo")); - }) - ) - ); - ly.AlternativeWindow(aw => aw - .Name("OtherWindow") - .Title("Secondary window (HWND)") - .Dimensions(340, 190) - .Background(Color.LightSkyBlue) - .Content(c => c.Children(ch => - { - ch.View(lb => lb.Text("Close it and show it again: it recreates itself.")); - ch.View(b => b.Name("BtnOtherHide").Text("Hide")); - ch.View(b => b.Name("BtnOtherClose").Text("Close")); - })) + .Dimensions(525, 494) + .SystemTray(st => st.Tooltip("EWA - Demo")) ); ly.AlternativeWindow(m => m .Name("GalleryMenu") .Content(c => c.Children(ch => { - ch.View(mi => mi.Name("MiHello").Text("To greet")); + ch.View(mi => mi.Name("MiShowMainWin").Text("Go to main window")); ch.View(); ch.View(mi => mi.Name("MiExit").Text("Exit")); })) ); - ly.AlternativeWindow(td => td - .Name("TaskDialog") - .Title("Complete TaskDialog") - .Content(c => - { - c.NotificationIcon(TaskDialogIcon.Information); - c.PrimaryText("Builder mode .Content()"); - c.SecondaryText("Choices Yes/No, Default choice No."); - c.Choices(TaskDialogChoices.Yes | TaskDialogChoices.No); - c.DefaultChoice(TaskDialogChoice.No); - }) - ); - ly.AlternativeWindow(td => td - .Name("TaskDialogMin") - .Title("Minimum TaskDialog") - .Text("Minimal mode .Text() — defaults: Ok, no icon.") - ); - ly.AlternativeWindow(ofd => ofd - .Name("OpenFileDialog") - .Title("Open image") - .Filters(f => f.Children(fc => fc - .FileFilter(PngFile) - .FileFilter(JpgFile) - .FileFilter(AllFile))) - .Content(c => c - .DefaultDirectory(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)) - .MultiSelect(true)) - ); - ly.AlternativeWindow(sfd => sfd - .Name("SaveFileDialog") - .Title("Save text") - .Filters(f => f.Children(fc => fc.FileFilter(TxtFile))) - .Content(c => c - .DefaultDirectory(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)) - .DefaultFileName("exit") - .DefaultExtension(TxtFile)) - ); - ly.AlternativeWindow(sfd => sfd - .Name("SelectFolderDialog") - .Title("Select folder") - .Content(c => c - .DefaultDirectory(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)) - .PersistLastDirectory(false)) - ); }) .Behavior(bh => { - bh.BtnAltWindow.OnInputWithSpatialPosition(() => bh.OtherWindow.Show()); - bh.BtnMenu.OnInputWithSpatialPosition(() => bh.GalleryMenu.Show()); - - bh.BtnTaskDialog.OnInputWithSpatialPosition(() => - { - var r = bh.TaskDialog.Show(); - bh.LbResult.Text = r.IsCanceled ? "TaskDialog: cancelado." : $"TaskDialog: {r.Choice}."; - }); - bh.BtnTaskDialogMin.OnInputWithSpatialPosition(() => - { - var r = bh.TaskDialogMin.Show(); - bh.LbResult.Text = r.IsCanceled ? "TaskDialog mín: cancelado." : $"TaskDialog mín: {r.Choice}."; - }); - bh.BtnOpenFile.OnInputWithSpatialPosition(() => + bh.WindowsApplication.OnLaunched(wa => { - var r = bh.OpenFileDialog.Show(); - bh.LbResult.Text = r.IsCanceled ? "OpenFile: cancelado." - : $"OpenFile: {r.FilePaths.Count} archivo(s) — {string.Join(", ", r.FilePaths)}"; + wa.TaskbarButtonVisibility(false); + bh.MainWindow.Visibility(false); + bh.SystemTray.Visibility(true); }); - bh.BtnSaveFile.OnInputWithSpatialPosition(() => - { - var r = bh.SaveFileDialog.Show(); - bh.LbResult.Text = r.IsCanceled ? "SaveFile: cancelado." : $"SaveFile: {r.FilePath}"; - }); - bh.BtnSelectFolder.OnInputWithSpatialPosition(() => - { - var r = bh.SelectFolderDialog.Show(); - bh.LbResult.Text = r.IsCanceled ? "SelectFolder: cancelado." : $"SelectFolder: {r.FolderPath}"; - }); - bh.OtherWindow.OnLoaded(() => bh.LbResult.Text = "OtherWindow: Loaded (ciclo fresco)."); - bh.OtherWindow.OnClosing(e => - { - bh.LbResult.Text = "OtherWindow: Closing..."; - System.Threading.Thread.Sleep(2000); - }); - bh.OtherWindow.OnClosed(() => bh.LbResult.Text = "OtherWindow: Closed — Show() la re-crea."); - bh.BtnOtherHide.OnInputWithSpatialPosition(() => bh.OtherWindow.Visibility(false)); - bh.BtnOtherClose.OnInputWithSpatialPosition(() => bh.OtherWindow.Close()); - // Phase C/D: ventana principal (State + Z-order) - bh.MainWindow.OnResized(a => bh.LbResult.Text = $"MainWindow: Resized {a.Width}×{a.Height} (accepted)"); - bh.BtnWinState.OnInputWithSpatialPosition(() => - { - IWindow win = bh.MainWindow; - // Demo: ciclo Normal → FullScreen → Maximize → Normal - switch (win.State) - { - case WindowState.Normal: win.State = WindowState.FullScreen; break; - case WindowState.FullScreen: win.ExitFullScreen(); break; - case WindowState.Maximized: win.Minimize(); break; - case WindowState.Minimized: win.RestoreState(); break; - } - bh.LbResult.Text = $"MainWindow: State = {win.State} | {win.MaxSize}"; - }); - bh.BtnZOrder.OnInputWithSpatialPosition(() => - { - // Traer al frente, luego al fondo 1 segundo después (visible en la taskbar) - ((IWindow)bh.MainWindow).BringToFront(); - bh.LbResult.Text = "MainWindow: BringToFront ejecutado (que la haga visible encima de otras apps)"; - }); - bh.MiHello.OnInputWithSpatialPosition(() => bh.LbResult.Text = "Menú: ¡Hola!"); + bh.SystemTray.OnInputWithSpatialPosition(() => bh.MainWindow.Show()); + + bh.SystemTray.OnInputWithSpatialPosition(() => bh.GalleryMenu.Show()); + bh.MiExit.OnInputWithSpatialPosition(() => bh.WindowsApplication.Terminate()); - bh.WindowsApplication.OnTerminated(() => System.Diagnostics.Trace.WriteLine("[EWA] Terminated")); - bh.MainWindow.OnInputWithoutSpatialPosition(() => - { - if (bh.MainWindow.State is WindowState.FullScreen) - { - bh.MainWindow.RestoreState(); - } - }); + + bh.MiShowMainWin.OnInputWithSpatialPosition(() => bh.MainWindow.Show()); }) .Initialize();