From b0d2e9b47091f66d8d176256d6daab6b46cc3dbe Mon Sep 17 00:00:00 2001 From: Msystech Date: Fri, 14 Aug 2026 15:06:08 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B0=80=EB=A6=BC=EB=A7=89=EC=9D=B4=20?= =?UTF-8?q?=EB=8C=80=ED=99=94=EC=83=81=EC=9E=90=20=EC=9C=84=EC=97=90=20?= =?UTF-8?q?=EC=9E=88=EC=97=88=EB=8B=A4=20=E2=80=94=20z=EC=88=9C=EC=84=9C?= =?UTF-8?q?=EB=A5=BC=20=EB=AA=85=EC=8B=9C=EC=A0=81=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=82=B4=EB=A6=B0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 두 번째 오진이다. 지난번에는 가림막이 아예 안 깔렸고, 고친 뒤에는 대화상자 위에 깔렸다. 그래서 대화상자까지 같이 흐려져 글자가 눌려 보였다. 원인: 가림막과 대화상자가 같은 창을 소유자로 갖는 형제라, 나중에 띄운 가림막이 위에 온다. Activate() 로는 안 된다 — 활성 창을 바꾸는 것과 z순서를 바꾸는 것은 다른 일이다. SetWindowPos 로 가림막을 대화상자 바로 아래에 꽂는다. 두 번 다 "코드를 읽어서는 맞아 보이는데 화면은 다른" 경우였다. 그래서 --modal-check 에 z순서 검사를 더했다 — 창 개수만 세면 위에 있는지 아래에 있는지 모른다. GetWindow(GW_HWNDNEXT) 로 대화상자 다음 창이 가림막인지 본다. 실측: 떠 있는 동안 가림막 1개 · 대화상자 아래에 있음 · 닫으면 0개. 게이트: 테스트 277/277, --edit-smoke 0실패, --modal-check 3/3(1건 신규), --dialog-shots 57파일, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 --- .../Diagnostics/ModalCheck.cs | 26 +++++++++++++++++ src/SheetMe.Designer/Services/ModalScrim.cs | 28 +++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/SheetMe.Designer/Diagnostics/ModalCheck.cs b/src/SheetMe.Designer/Diagnostics/ModalCheck.cs index daf15a1..320eaa1 100644 --- a/src/SheetMe.Designer/Diagnostics/ModalCheck.cs +++ b/src/SheetMe.Designer/Diagnostics/ModalCheck.cs @@ -71,6 +71,7 @@ public static class ModalCheck }; var scrimsWhileOpen = -1; + var dialogAbove = false; var modalAtLoaded = false; dialog.Loaded += (_, _) => modalAtLoaded = System.Windows.Interop.ComponentDispatcher.IsThreadModal; @@ -85,6 +86,7 @@ public static class ModalCheck timer.Stop(); modalAtProbe = System.Windows.Interop.ComponentDispatcher.IsThreadModal; scrimsWhileOpen = CountScrims(); + dialogAbove = DialogIsAboveScrim(dialog); dialog.Close(); }; timer.Start(); @@ -97,6 +99,8 @@ public static class ModalCheck Check("모달일 때 가림막이 깔린다", scrimsWhileOpen == 1, $"실제 {scrimsWhileOpen}개"); + // 가림막이 대화상자 위에 오면 대화상자까지 같이 흐려진다 — 실제로 그렇게 만들었다 + Check("가림막이 대화상자 아래에 있다", dialogAbove); Check("닫으면 가림막이 걷힌다", CountScrims() == 0, $"실제 {CountScrims()}개"); owner.Close(); @@ -112,6 +116,28 @@ public static class ModalCheck return failed == 0 ? 0 : 1; } + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern IntPtr GetWindow(IntPtr hwnd, uint command); + + /// + /// 대화상자 바로 다음(= 아래) 창이 가림막인가. + /// GW_HWNDNEXT = 2 는 z순서에서 이 창 다음에 오는 창을 준다. + /// + private static bool DialogIsAboveScrim(Window dialog) + { + var dialogHandle = new System.Windows.Interop.WindowInteropHelper(dialog).Handle; + var next = GetWindow(dialogHandle, 2); + foreach (Window window in Application.Current.Windows) + { + if (window.AllowsTransparency && !window.ShowInTaskbar + && new System.Windows.Interop.WindowInteropHelper(window).Handle == next) + { + return true; + } + } + return false; + } + /// 가림막은 투명 + 작업표시줄 미표시 + 소유자 있는 창이다 private static int CountScrims() { diff --git a/src/SheetMe.Designer/Services/ModalScrim.cs b/src/SheetMe.Designer/Services/ModalScrim.cs index e1dca07..4d62849 100644 --- a/src/SheetMe.Designer/Services/ModalScrim.cs +++ b/src/SheetMe.Designer/Services/ModalScrim.cs @@ -75,8 +75,12 @@ public static class ModalScrim behind.StateChanged += Follow; scrim.Show(); - // 가림막을 나중에 띄웠으니 대화상자를 다시 앞으로 올린다 - window.Activate(); + // 가림막을 대화상자 바로 아래로 내린다. + // + // Activate() 로는 안 된다. 둘 다 같은 창을 소유자로 갖는 형제라 나중에 띄운 쪽이 위에 오고, + // 활성 창을 바꾸는 것과 z순서를 바꾸는 것은 다른 일이다 — + // 실제로 대화상자까지 같이 흐려졌다. + PlaceBehind(scrim, window); Layers.Push(scrim); window.Closed += (_, _) => @@ -92,6 +96,26 @@ public static class ModalScrim }; } + [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] + private static extern bool SetWindowPos(IntPtr hwnd, IntPtr insertAfter, + int x, int y, int cx, int cy, uint flags); + + /// + /// 바로 아래로 놓는다. + /// SetWindowPos 의 insertAfter 는 "이 창 다음"이라는 뜻이라, 대화상자를 넘기면 그 아래가 된다. + /// + private static void PlaceBehind(Window scrim, Window above) + { + var scrimHandle = new System.Windows.Interop.WindowInteropHelper(scrim).Handle; + var aboveHandle = new System.Windows.Interop.WindowInteropHelper(above).Handle; + if (scrimHandle == IntPtr.Zero || aboveHandle == IntPtr.Zero) + { + return; + } + // SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE + SetWindowPos(scrimHandle, aboveHandle, 0, 0, 0, 0, 0x0002 | 0x0001 | 0x0010); + } + /// 뒤 창을 정확히 덮는다 — 최대화 상태도 그 사각형 그대로다 private static void Cover(Window scrim, Window behind) {