가림막이 대화상자 위에 있었다 — z순서를 명시적으로 내린다

두 번째 오진이다. 지난번에는 가림막이 아예 안 깔렸고, 고친 뒤에는 <b>대화상자 위에</b> 깔렸다.
그래서 대화상자까지 같이 흐려져 글자가 눌려 보였다.

원인: 가림막과 대화상자가 같은 창을 소유자로 갖는 형제라, 나중에 띄운 가림막이 위에 온다.
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 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-14 15:06:08 +09:00
co-authored by Claude Opus 5
parent 7e0e201acb
commit b0d2e9b470
2 changed files with 52 additions and 2 deletions
@@ -71,6 +71,7 @@ public static class ModalCheck
}; };
var scrimsWhileOpen = -1; var scrimsWhileOpen = -1;
var dialogAbove = false;
var modalAtLoaded = false; var modalAtLoaded = false;
dialog.Loaded += (_, _) => dialog.Loaded += (_, _) =>
modalAtLoaded = System.Windows.Interop.ComponentDispatcher.IsThreadModal; modalAtLoaded = System.Windows.Interop.ComponentDispatcher.IsThreadModal;
@@ -85,6 +86,7 @@ public static class ModalCheck
timer.Stop(); timer.Stop();
modalAtProbe = System.Windows.Interop.ComponentDispatcher.IsThreadModal; modalAtProbe = System.Windows.Interop.ComponentDispatcher.IsThreadModal;
scrimsWhileOpen = CountScrims(); scrimsWhileOpen = CountScrims();
dialogAbove = DialogIsAboveScrim(dialog);
dialog.Close(); dialog.Close();
}; };
timer.Start(); timer.Start();
@@ -97,6 +99,8 @@ public static class ModalCheck
Check("모달일 때 가림막이 깔린다", scrimsWhileOpen == 1, Check("모달일 때 가림막이 깔린다", scrimsWhileOpen == 1,
$"실제 {scrimsWhileOpen}개"); $"실제 {scrimsWhileOpen}개");
// 가림막이 대화상자 위에 오면 대화상자까지 같이 흐려진다 — 실제로 그렇게 만들었다
Check("가림막이 대화상자 아래에 있다", dialogAbove);
Check("닫으면 가림막이 걷힌다", CountScrims() == 0, $"실제 {CountScrims()}개"); Check("닫으면 가림막이 걷힌다", CountScrims() == 0, $"실제 {CountScrims()}개");
owner.Close(); owner.Close();
@@ -112,6 +116,28 @@ public static class ModalCheck
return failed == 0 ? 0 : 1; return failed == 0 ? 0 : 1;
} }
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hwnd, uint command);
/// <summary>
/// 대화상자 바로 다음(= 아래) 창이 가림막인가.
/// GW_HWNDNEXT = 2 는 z순서에서 이 창 다음에 오는 창을 준다.
/// </summary>
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;
}
/// <summary>가림막은 투명 + 작업표시줄 미표시 + 소유자 있는 창이다</summary> /// <summary>가림막은 투명 + 작업표시줄 미표시 + 소유자 있는 창이다</summary>
private static int CountScrims() private static int CountScrims()
{ {
+26 -2
View File
@@ -75,8 +75,12 @@ public static class ModalScrim
behind.StateChanged += Follow; behind.StateChanged += Follow;
scrim.Show(); scrim.Show();
// 가림막을 나중에 띄웠으니 대화상자를 다시 앞으린다 // 가림막을 대화상자 <b>바로 아래</b>린다.
window.Activate(); //
// Activate() 로는 안 된다. 둘 다 같은 창을 소유자로 갖는 형제라 나중에 띄운 쪽이 위에 오고,
// 활성 창을 바꾸는 것과 z순서를 바꾸는 것은 다른 일이다 —
// 실제로 대화상자까지 같이 흐려졌다.
PlaceBehind(scrim, window);
Layers.Push(scrim); Layers.Push(scrim);
window.Closed += (_, _) => 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);
/// <summary>
/// <paramref name="scrim"/> 을 <paramref name="above"/> 바로 아래로 놓는다.
/// SetWindowPos 의 insertAfter 는 "이 창 다음"이라는 뜻이라, 대화상자를 넘기면 그 아래가 된다.
/// </summary>
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);
}
/// <summary>뒤 창을 정확히 덮는다 — 최대화 상태도 그 사각형 그대로다</summary> /// <summary>뒤 창을 정확히 덮는다 — 최대화 상태도 그 사각형 그대로다</summary>
private static void Cover(Window scrim, Window behind) private static void Cover(Window scrim, Window behind)
{ {