diff --git a/src/SheetMe.Designer/Controls/DialogChrome.cs b/src/SheetMe.Designer/Controls/DialogChrome.cs new file mode 100644 index 0000000..02aec05 --- /dev/null +++ b/src/SheetMe.Designer/Controls/DialogChrome.cs @@ -0,0 +1,90 @@ +using System.Windows; +using System.Windows.Input; + +namespace SheetMe.Designer.Controls; + +/// +/// 대화상자 크롬을 켠다 — ThemedDialogWindow 스타일이 스타일 세터로 걸어 준다. +/// +/// 왜 첨부 속성인가. 창 12종에 같은 타이틀바를 주려면 이 필요한데, +/// 템플릿 안의 닫기 버튼은 Click 핸들러를 가질 수 없다(템플릿은 코드비하인드가 없다). +/// 그래서 표준 를 쓰고, 그 명령을 받을 +/// 을 여기서 창에 달아 준다. 창마다 코드를 넣지 않아도 된다. +/// +/// 최대화 보정도 함께 건다. WindowStyle=None 인 창은 최대화하는 순간 +/// 작업표시줄을 덮는다(메인 창에서 실제로 겪은 결함이다). 대화상자 대부분이 크기 조절 가능이라 +/// 타이틀바를 더블클릭하면 그 상태에 들어갈 수 있다 — 크롬을 켜는 이 자리에서 같이 막는다. +/// +public static class DialogChrome +{ + #region Attached Properties + /// 크롬 사용 여부 — 스타일이 True 로 세팅한다 + public static readonly DependencyProperty EnabledProperty = + DependencyProperty.RegisterAttached( + "Enabled", typeof(bool), typeof(DialogChrome), + new PropertyMetadata(false, OnEnabledChanged)); + + public static void SetEnabled(DependencyObject element, bool value) + => element.SetValue(EnabledProperty, value); + + public static bool GetEnabled(DependencyObject element) + => (bool)element.GetValue(EnabledProperty); + #endregion + + #region Methods + private static void OnEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (sender is not Window window || e.NewValue is not true) + { + return; + } + // 같은 창에 두 번 걸리지 않게 한다 — 스타일이 다시 적용되는 경로가 있다 + foreach (CommandBinding existing in window.CommandBindings) + { + if (existing.Command == SystemCommands.CloseWindowCommand) + { + return; + } + } + // 크롬은 창마다 새로 만든다. WindowChrome 은 Freezable 이고 창에 붙으면서 + // 스스로 속성을 고치므로, 한 인스턴스를 여러 창이 나눠 쓰면 두 번째 창부터 + // "읽기 전용 상태라 속성을 설정할 수 없습니다"로 죽는다. + // 그래서 XAML 스타일 세터로는 줄 수 없다(세터 값은 한 번 평가돼 공유된다). + // + // CaptionHeight 36 = 타이틀바 높이와 같아야 그 띠를 잡고 창을 끌 수 있다. + // GlassFrameThickness 0,0,0,1 = DWM 그림자 유지. + // ResizeBorderThickness 6 은 ResizeMode 가 허용할 때만 실제로 동작한다. + System.Windows.Shell.WindowChrome.SetWindowChrome(window, new System.Windows.Shell.WindowChrome + { + CaptionHeight = 36, + ResizeBorderThickness = new Thickness(6), + GlassFrameThickness = new Thickness(0, 0, 0, 1), + CornerRadius = new CornerRadius(0), + UseAeroCaptionButtons = false, + }); + + window.CommandBindings.Add(new CommandBinding( + SystemCommands.CloseWindowCommand, + (_, args) => + { + // 창의 X 를 누른 것과 같아야 한다 — DialogResult 를 건드리지 않으므로 + // ShowDialog 는 false 를 돌려주고, 호출부의 '취소' 경로가 그대로 탄다 + SystemCommands.CloseWindow(window); + args.Handled = true; + })); + + // 크기 조절 가능한 창만 최대화될 수 있다 — 그때 작업표시줄을 덮지 않게 한다 + if (window.ResizeMode is ResizeMode.CanResize or ResizeMode.CanResizeWithGrip) + { + if (window.IsInitialized) + { + Services.MaximizeToWorkArea.Attach(window); + } + else + { + window.SourceInitialized += (_, _) => Services.MaximizeToWorkArea.Attach(window); + } + } + } + #endregion +} diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs index 22f255c..d3a0f68 100644 --- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs +++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs @@ -390,18 +390,26 @@ public static class DialogShots // 레이아웃·비동기 로딩(Loaded 핸들러)이 한 바퀴 돌게 한다 Pump(); - // 창 자체를 렌더한다 — Content 만 찍으면 Window.Background(B.AppBg)가 빠져 투명이 되고, - // PNG 에서 검정으로 저장돼 "다크처럼 보이는" 착시가 생긴다(라이트 결함이 가려진다). + // 창 전체를 찍는다 — Content 만 찍으면 타이틀바가 통째로 빠진다. + // + // 전에는 Content 를 찍었다. 그래서 창 크롬을 통일하기 전까지 이 진단은 + // "열한 창이 윈도우 기본 타이틀바를 달고 뜬다"는 사실을 한 번도 보여 준 적이 없었다 — + // 고쳐야 할 바로 그 부분에 눈이 없었던 셈이다. 이제 커스텀 타이틀바는 창의 Template + // 안에 있으므로 Content 만 찍으면 여전히 안 보인다. + // + // 배경을 먼저 칠하는 것은 그대로 둔다 — 투명 픽셀이 PNG 에서 검정으로 저장되면 + // 라이트 테마 결함이 "다크처럼 보이는" 착시에 가려진다. var content = (FrameworkElement)window.Content; - var width = (int)Math.Ceiling(content.ActualWidth > 0 ? content.ActualWidth : window.Width); - var height = (int)Math.Ceiling(content.ActualHeight > 0 ? content.ActualHeight : window.Height); + var root = (FrameworkElement?)window.Template?.FindName("PART_Root", window) ?? content; + var width = (int)Math.Ceiling(window.ActualWidth > 0 ? window.ActualWidth : window.Width); + var height = (int)Math.Ceiling(window.ActualHeight > 0 ? window.ActualHeight : window.Height); var bitmap = new RenderTargetBitmap(Math.Max(1, width), Math.Max(1, height), 96, 96, PixelFormats.Pbgra32); var drawing = new DrawingVisual(); using (var context = drawing.RenderOpen()) { context.DrawRectangle(window.Background ?? Brushes.Transparent, null, new Rect(0, 0, width, height)); - context.DrawRectangle(new VisualBrush(content) { Stretch = Stretch.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top }, + context.DrawRectangle(new VisualBrush(root) { Stretch = Stretch.None, AlignmentX = AlignmentX.Left, AlignmentY = AlignmentY.Top }, null, new Rect(0, 0, width, height)); } bitmap.Render(drawing); diff --git a/src/SheetMe.Designer/Themes/DesignerTheme.xaml b/src/SheetMe.Designer/Themes/DesignerTheme.xaml index 68c080f..fe94043 100644 --- a/src/SheetMe.Designer/Themes/DesignerTheme.xaml +++ b/src/SheetMe.Designer/Themes/DesignerTheme.xaml @@ -1404,4 +1404,68 @@ + + + diff --git a/src/SheetMe.Designer/Views/DataSourceResultDialogView.xaml b/src/SheetMe.Designer/Views/DataSourceResultDialogView.xaml index 569c476..5cf8c49 100644 --- a/src/SheetMe.Designer/Views/DataSourceResultDialogView.xaml +++ b/src/SheetMe.Designer/Views/DataSourceResultDialogView.xaml @@ -4,7 +4,7 @@ Title="데이터소스 조회 결과" Width="980" Height="640" MinWidth="760" MinHeight="440" WindowStartupLocation="CenterOwner" ShowInTaskbar="False" - Style="{StaticResource ThemedWindow}"> + Style="{StaticResource ThemedDialogWindow}">