모달 뒤를 어둡게 — 대화상자가 떠 있다는 것이 보이게
WPF 의 ShowDialog 는 입력만 막고 화면은 그대로 둔다. 대화상자가 앱과 같은 밝기로 떠 있으면 "이게 떠 있는 동안은 뒤를 못 만진다"가 안 읽혀서, 뒤를 클릭했다가 아무 반응이 없으면 멈춘 것으로 오해한다. 창이 뜨는 순간을 전역으로 잡아 모달일 때만 반투명 검정 창을 뒤에 깐다. 호출부마다 고치지 않은 이유는 ShowDialog 가 17곳이고 앞으로 더 늘기 때문이다 — 한 곳이라도 빠지면 그 대화상자만 다르게 동작하는데 그건 눈으로 전수 확인해야만 알 수 있다. 제목 표시줄 색을 거는 자리(WindowChromeTheme)와 같은 곳에 나란히 걸었다. 모달 판정은 ComponentDispatcher.IsThreadModal 이다. 진단 모드는 창을 모달이 아니라 화면 밖에 그냥 띄우므로 이 경로를 안 탄다 — --dialog-shots 57파일 그대로다. 가림막은 뒤 창의 이동·크기·상태 변화를 따라간다. 안 따라가면 창을 옮겼을 때 어두운 사각형만 제자리에 남는다. 최대화된 창은 Left/Top 이 '복원했을 때의 값'이라 그대로 쓰면 엉뚱한 데 깔리므로 화면 좌표를 직접 물어 DIP 로 되돌린다. 대화상자가 겹쳐 뜨면 그만큼 쌓이고, 각자 닫힐 때 제 것만 걷는다. 게이트: 테스트 267/267, --edit-smoke 0실패, --query-popup 17/17, --maxrect 10/10, --db-smoke 3000 diff 0·예외 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --dialog-shots 57파일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
893d81a40e
commit
d9c1eaa293
@@ -0,0 +1,108 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
|
||||
namespace SheetMe.Designer.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 모달 대화상자 뒤를 어둡게 덮는다.
|
||||
///
|
||||
/// <b>왜 필요한가.</b> WPF 의 <c>ShowDialog()</c> 는 입력만 막고 화면은 그대로 둔다.
|
||||
/// 대화상자가 앱과 같은 밝기로 떠 있으면 "이게 떠 있는 동안은 뒤를 못 만진다"가 안 읽혀서,
|
||||
/// 뒤를 클릭했다가 아무 반응이 없으면 멈춘 것으로 오해한다.
|
||||
///
|
||||
/// <b>왜 호출부마다 안 고쳤나.</b> ShowDialog 호출부가 17곳이고 앞으로 더 는다.
|
||||
/// 한 곳이라도 빠지면 그 대화상자만 다르게 동작하는데, 그건 눈으로 전수 확인해야만 알 수 있다.
|
||||
/// 창이 뜨는 순간을 전역으로 잡아 <b>모달일 때만</b> 건다(WindowChromeTheme 이 제목 표시줄 색을
|
||||
/// 같은 방식으로 거는 것과 같은 자리다).
|
||||
///
|
||||
/// 진단 모드는 창을 모달이 아니라 화면 밖에 그냥 띄우므로 이 경로를 타지 않는다 —
|
||||
/// --dialog-shots 산출물은 영향받지 않는다.
|
||||
/// </summary>
|
||||
public static class ModalScrim
|
||||
{
|
||||
#region Member Fields
|
||||
/// <summary>지금 떠 있는 가림막 — 대화상자가 겹쳐 뜨면 그만큼 쌓인다</summary>
|
||||
private static readonly Stack<Window> Layers = new();
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
/// <summary>
|
||||
/// 이 창이 모달이면 뒤에 가림막을 깐다 — 창이 <b>보이기 시작할 때</b> 불러야 한다.
|
||||
/// </summary>
|
||||
public static void Attach(Window window)
|
||||
{
|
||||
// 모달이 아닌 창(메인 셸, 진단 렌더용 오프스크린 창)은 대상이 아니다
|
||||
if (!System.Windows.Interop.ComponentDispatcher.IsThreadModal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var behind = window.Owner ?? Application.Current?.MainWindow;
|
||||
if (behind is null || ReferenceEquals(behind, window) || !behind.IsVisible)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scrim = new Window
|
||||
{
|
||||
WindowStyle = WindowStyle.None,
|
||||
ResizeMode = ResizeMode.NoResize,
|
||||
AllowsTransparency = true,
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x66, 0, 0, 0)),
|
||||
ShowInTaskbar = false,
|
||||
ShowActivated = false,
|
||||
IsHitTestVisible = false,
|
||||
Focusable = false,
|
||||
WindowStartupLocation = WindowStartupLocation.Manual,
|
||||
Owner = behind,
|
||||
};
|
||||
Cover(scrim, behind);
|
||||
// 뒤 창이 움직이거나 크기가 바뀌면 가림막도 따라가야 한다 — 안 그러면 어두운 사각형만 남는다
|
||||
void Follow(object? sender, EventArgs e) => Cover(scrim, behind);
|
||||
behind.LocationChanged += Follow;
|
||||
behind.SizeChanged += Follow;
|
||||
behind.StateChanged += Follow;
|
||||
|
||||
scrim.Show();
|
||||
// 가림막을 나중에 띄웠으니 대화상자를 다시 앞으로 올린다
|
||||
window.Activate();
|
||||
Layers.Push(scrim);
|
||||
|
||||
window.Closed += (_, _) =>
|
||||
{
|
||||
behind.LocationChanged -= Follow;
|
||||
behind.SizeChanged -= Follow;
|
||||
behind.StateChanged -= Follow;
|
||||
if (Layers.Count > 0)
|
||||
{
|
||||
Layers.Pop();
|
||||
}
|
||||
scrim.Close();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>뒤 창을 정확히 덮는다 — 최대화 상태도 그 사각형 그대로다</summary>
|
||||
private static void Cover(Window scrim, Window behind)
|
||||
{
|
||||
if (behind.WindowState == WindowState.Minimized)
|
||||
{
|
||||
scrim.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
scrim.Visibility = Visibility.Visible;
|
||||
|
||||
// 최대화된 창은 Left/Top 이 '복원했을 때의 값'이라 그대로 쓰면 가림막이 엉뚱한 데 깔린다.
|
||||
// 화면 좌표를 직접 물어 DIP 로 되돌린다.
|
||||
var origin = new Point(behind.Left, behind.Top);
|
||||
if (PresentationSource.FromVisual(behind) is { CompositionTarget: { } target })
|
||||
{
|
||||
origin = target.TransformFromDevice.Transform(behind.PointToScreen(new Point(0, 0)));
|
||||
}
|
||||
scrim.Left = origin.X;
|
||||
scrim.Top = origin.Y;
|
||||
scrim.Width = behind.ActualWidth > 0 ? behind.ActualWidth : behind.Width;
|
||||
scrim.Height = behind.ActualHeight > 0 ? behind.ActualHeight : behind.Height;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user