diff --git a/FontManagerDialogView.xaml.cs b/FontManagerDialogView.xaml.cs
new file mode 100644
index 0000000..e69de29
diff --git a/RecordWordDialogView.xaml.cs b/RecordWordDialogView.xaml.cs
new file mode 100644
index 0000000..e69de29
diff --git a/SheetOpenDialogView.xaml.cs b/SheetOpenDialogView.xaml.cs
new file mode 100644
index 0000000..e69de29
diff --git a/src/SheetMe.Designer/App.xaml.cs b/src/SheetMe.Designer/App.xaml.cs
index b46d635..46327dd 100644
--- a/src/SheetMe.Designer/App.xaml.cs
+++ b/src/SheetMe.Designer/App.xaml.cs
@@ -46,6 +46,12 @@ public partial class App : Application
var config = Services.ConfigService.Current;
if (!Services.SessionBootstrap.TryInitialize(launch, config.ConnectionString, config.DevUidCod, out var error))
{
+ // 순정 MessageBox 유지 — 테마 대화상자로 바꾸지 말 것.
+ // (1) 이 시점엔 창이 하나도 없다. 기본 ShutdownMode 는 OnLastWindowClose 라 여기서 Window 를
+ // 띄우면 그 창이 앱의 유일한 창이 되고, 닫는 순간 WPF 가 자동 종료를 시작해 바로 아래
+ // Shutdown(2) 의 종료코드가 유실될 수 있다(런처가 실패를 성공으로 읽는다).
+ // (2) ThemeManager.LoadSaved() 가 아직 아래(:54)라 라이트 사용자에게도 다크 창이 뜬다.
+ // (3) '앱이 뜨지 못한다'를 알리는 마지막 통로다 — 알림 수단이 앱 상태에 의존하면 안 된다.
MessageBox.Show(error, "서식생성기", MessageBoxButton.OK, MessageBoxImage.Error);
Shutdown(2);
return;
@@ -77,19 +83,24 @@ public partial class App : Application
{
recentCrashes.Dequeue();
}
+ // 아래 두 알림은 순정 MessageBox 를 유지한다 — 테마 대화상자로 바꾸지 말 것.
+ // 여기는 이미 예외가 터진 자리다. WPF 창을 새로 만들면(템플릿 해석·DynamicResource·렌더)
+ // 그 과정이 다시 던져 같은 핸들러로 재진입한다 — 무한 팝업을 막으려는 코드가 원인이 된다.
+ // MessageBox 는 Win32 호출이라 WPF 렌더 스택에 의존하지 않는다.
if (recentCrashes.Count >= 5)
{
+ e.Handled = true;
MessageBox.Show("반복되는 오류로 프로그램을 종료합니다.\n로그를 확인해 주세요.\n오류 코드: " + code,
"서식생성기", MessageBoxButton.OK, MessageBoxImage.Error);
- e.Handled = true;
Shutdown(3);
return;
}
+ // Handled 를 알림보다 먼저 세운다 — 알림이 던지면 '복구 가능한 예외'가 하드 크래시로 바뀐다
+ e.Handled = true;
MessageBox.Show(
$"오류가 발생했지만 작업은 계속할 수 있습니다.\n저장하지 않은 내용이 있으면 먼저 저장해 주세요.\n\n오류 코드: {code}",
"서식생성기", MessageBoxButton.OK, MessageBoxImage.Warning);
- e.Handled = true;
}
/// 진단 플래그 분기 — 종료 코드를 반환한다(호출부가 Shutdown 처리)
@@ -176,8 +187,11 @@ public partial class App : Application
return Diagnostics.DialogShots.Run(args[1]);
}
- MessageBox.Show($"알 수 없는 진단 옵션입니다: {args[0]}", "서식생성기",
- MessageBoxButton.OK, MessageBoxImage.Warning);
+ // 진단 모드에서는 모달을 띄우지 않는다 — 스크립트 옵션 오타 하나로 무인 실행이 여기서
+ // 영원히 멈춘다. WinExe 라 콘솔이 없어 stderr 는 호출자가 리다이렉트할 때만 보이므로
+ // 로그를 함께 남긴다(--db-render 가 .err.txt 로 남기는 것과 같은 이유).
+ Services.AppLog.Warn($"알 수 없는 진단 옵션입니다: {args[0]}");
+ Console.Error.WriteLine($"알 수 없는 진단 옵션입니다: {args[0]}");
return 2;
}
diff --git a/src/SheetMe.Designer/Controls/LucideIcons.cs b/src/SheetMe.Designer/Controls/LucideIcons.cs
index e3e4e99..c8fded4 100644
--- a/src/SheetMe.Designer/Controls/LucideIcons.cs
+++ b/src/SheetMe.Designer/Controls/LucideIcons.cs
@@ -36,6 +36,13 @@ public static class LucideIcons
["scroll-text"] = """ """,
["search"] = """ """,
["circle-check"] = """ """,
+ // 알림 대화상자 성격 아이콘 4종.
+ // 점은 `M12 8h.01` 같은 퇴화 세그먼트가 아니라 fill 원으로 넣는다 — key-round 가 이미 쓰는
+ // 경로라 Parse 의 fill="currentColor" 분기가 확실히 처리한다.
+ ["info"] = """ """,
+ ["circle-alert"] = """ """,
+ ["triangle-alert"] = """ """,
+ ["circle-help"] = """ """,
["type"] = """ """,
["text-cursor-input"] = """ """,
["calendar"] = """ """,
diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs
index e4ab742..da3c7a3 100644
--- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs
+++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs
@@ -133,6 +133,27 @@ public static class DialogShots
("14-layer-plain", () => HostLayerPanel(designer, LayerShotMode.None)),
("15-layer-selected", () => HostLayerPanel(designer, LayerShotMode.Single)),
("16-layer-group", () => HostLayerPanel(designer, LayerShotMode.Group)),
+ // 알림 대화상자 — 성격 4종과 버튼 3종의 대표 조합. 순정 MessageBox 를 걷어낸 자리라
+ // 라이트/다크 회귀를 여기서 잡는다.
+ ("17-msg-error", () => new Views.MessageDialogView(
+ Services.DialogKind.Error, "오류", "DB 저장 중 오류가 발생했습니다. (트랜잭션 롤백됨)",
+ "ORA-00001: unique constraint (MSYSTECHHIS.PK_E_SDGMST) violated\n오류 코드: 20260813-0007",
+ Services.DialogButtons.Ok)),
+ ("18-msg-warning", () => new Views.MessageDialogView(
+ Services.DialogKind.Warning, "DB 저장", "DB 저장이 비활성화되어 있습니다.",
+ "appsettings.json 의 FormStore:SaveMode 를 'Db' 로 설정한 뒤 사용하세요.\n" +
+ "(운영 레거시 테이블에 기록되므로 명시적 활성화가 필요합니다)",
+ Services.DialogButtons.Ok)),
+ ("19-msg-confirm", () => new Views.MessageDialogView(
+ Services.DialogKind.Question, "페이지 삭제", "페이지 2을(를) 삭제할까요?",
+ "컨트롤 116개가 함께 삭제됩니다.",
+ Services.DialogButtons.YesNo, yes: "삭제", no: "취소", destructive: true)),
+ ("20-msg-save", () => new Views.MessageDialogView(
+ Services.DialogKind.Question, "종료", "'P093 간호초기평가기록지_V2' 문서에 저장하지 않은 변경이 있습니다.",
+ "저장할까요?", Services.DialogButtons.YesNoCancel, yes: "저장", no: "저장 안 함")),
+ ("21-msg-info", () => new Views.MessageDialogView(
+ Services.DialogKind.Info, "DB 저장", "저장되었습니다. (SdgKey 52426)",
+ "레거시 뷰어/디자이너에서 열어 확인하세요.", Services.DialogButtons.Ok)),
};
}
diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
index a1dd8cc..596c0a4 100644
--- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
+++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
@@ -479,6 +479,14 @@ public static class EditSmoke
badgeTarget.Model.Props.SetText("DataTableField", string.Empty);
}
+ // 20-2-2) 알림이 무인 실행을 멈추지 않는다 — 진단 모드에서는 창을 만들지 않고 즉시
+ // 기각 기본값을 돌려줘야 한다. 이 규약이 깨지면 여기서 영원히 대기한다.
+ Check("알림: 진단 모드에서 창 없이 반환", Services.UserSession.IsDiagnostic);
+ Services.DialogService.Notify(Services.DialogKind.Warning, "스모크", "무인 실행 확인");
+ Check("알림: 확인 기본값 = 취소", !Services.DialogService.Confirm("스모크", "묻지 않고 지나가야 한다"));
+ Check("알림: 3버튼 기본값 = 취소",
+ Services.DialogService.ConfirmWithCancel("스모크", "묻지 않고 지나가야 한다") is null);
+
// 20-3) 문서 간 복사/붙여넣기 — A 서식에서 복사 → B 서식에 붙여넣기(클립보드 공유)
var designerB = new DesignerViewModel(business.CreateNew());
designer.Selection.SetSingle(Find("TextBox"));
diff --git a/src/SheetMe.Designer/Services/DialogService.cs b/src/SheetMe.Designer/Services/DialogService.cs
index 083ed66..b48daa8 100644
--- a/src/SheetMe.Designer/Services/DialogService.cs
+++ b/src/SheetMe.Designer/Services/DialogService.cs
@@ -1,8 +1,45 @@
+using System.Windows;
using Microsoft.Win32;
namespace SheetMe.Designer.Services;
-/// 파일 대화상자 래퍼 — ViewModel 에서 View 기술 의존을 격리.
+/// 알림 성격 — 아이콘과 강조색만 결정한다(버튼 구성과 무관)
+public enum DialogKind
+{
+ /// 정보 — 성공·빈 결과처럼 실패가 아닌 안내
+ Info,
+
+ /// 경고 — 거부·미설정·입력 오류
+ Warning,
+
+ /// 오류 — 예외
+ Error,
+
+ /// 확인 — 사용자에게 묻는 자리
+ Question,
+}
+
+/// 버튼 구성 — 실제로 쓰이는 3종이 전부다
+public enum DialogButtons
+{
+ /// 확인 1개
+ Ok,
+
+ /// 예 / 아니오
+ YesNo,
+
+ /// 예 / 아니오 / 취소
+ YesNoCancel,
+}
+
+///
+/// 대화상자 진입점 — 파일 대화상자 래퍼(ViewModel 에서 View 기술 의존 격리)와
+/// 앱 테마를 따르는 알림·확인 창.
+///
+/// 알림을 순정 MessageBox 대신 자체 창으로 띄우면서 새로 생긴 제약이 셋 있고,
+/// 그 셋을 이 클래스가 한곳에서 막는다 — 진단 모드에서는 창을 만들지 않고, UI 스레드가 아니면
+/// 넘겨 주며, 소유 창은 살아 있는 것만 건다. 호출부는 이걸 몰라도 된다.
+///
public sealed class DialogService
{
#region Methods
@@ -36,15 +73,94 @@ public sealed class DialogService
/// 담겨 있으므로 그대로 보여준다. 그 외(Oracle 오류·NRE 등)는 SQL 조각이나 접속 단서가 섞일 수 있어
/// 일반화 문구 + 오류 코드만 노출한다 — 코드는 로그 줄머리와 같아서 전화 한 통으로 특정된다.
///
- public static void ShowError(string action, Exception exception)
+ public static void ShowError(string action, Exception exception, Window? owner = null)
{
var code = AppLog.Error(action, exception);
var detail = exception is InvalidOperationException or ArgumentException
? exception.Message
: $"{exception.GetType().Name} — 자세한 내용은 로그를 확인해 주세요.\n오류 코드: {code}";
- System.Windows.MessageBox.Show($"{action} 중 오류가 발생했습니다.\n\n{AppLog.Redact(detail)}",
- "오류", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
+ Notify(DialogKind.Error, "오류", $"{action} 중 오류가 발생했습니다.",
+ AppLog.Redact(detail), owner);
}
+
+ /// 알림(확인 1개). 은 머리말 아래에 보조 글씨로 붙는다.
+ public static void Notify(DialogKind kind, string caption, string message,
+ string? detail = null, Window? owner = null)
+ => Ask(kind, caption, message, detail, DialogButtons.Ok, owner: owner);
+
+ /// 확인(예/아니오) — 예=true. 닫기·Esc 는 아니오로 친다.
+ /// 되돌리기 어려운 동작이면 기본 버튼을 부정 쪽에 둔다
+ public static bool Confirm(string caption, string message, string? detail = null,
+ string yes = "예", string no = "아니오", bool destructive = false, Window? owner = null)
+ => Ask(DialogKind.Question, caption, message, detail, DialogButtons.YesNo,
+ yes, no, destructive, owner) == true;
+
+ /// 확인(예/아니오/취소) — 취소=null. 닫기·Esc 는 취소로 친다.
+ public static bool? ConfirmWithCancel(string caption, string message, string? detail = null,
+ string yes = "예", string no = "아니오", Window? owner = null)
+ => Ask(DialogKind.Question, caption, message, detail, DialogButtons.YesNoCancel,
+ yes, no, destructive: false, owner);
+
+ ///
+ /// 실제 표시 — 세 가지 안전장치를 여기서 한 번에 건다.
+ ///
+ private static bool? Ask(DialogKind kind, string caption, string message, string? detail,
+ DialogButtons buttons, string yes = "예", string no = "아니오",
+ bool destructive = false, Window? owner = null)
+ {
+ // ① 진단 모드에서는 창을 만들지 않는다.
+ // 스모크는 무인 실행이라 모달이 하나라도 뜨면 타임아웃 없이 영원히 멈춘다.
+ // (실제로 편집 스모크가 지나는 경로에 붙여넣기·개명 가드가 있다)
+ if (UserSession.IsDiagnostic)
+ {
+ AppLog.Warn($"[진단] {caption}: {message}{(detail is null ? "" : " / " + detail)}");
+ return DismissValue(buttons);
+ }
+
+ var app = Application.Current;
+ if (app is null)
+ {
+ // 앱이 없는 호스트(단위 테스트 등) — 창을 만들 수 없다
+ AppLog.Warn($"[창 없음] {caption}: {message}");
+ return DismissValue(buttons);
+ }
+
+ // ② 스레드 친화성. MessageBox 는 Win32 호출이라 아무 스레드에서나 됐지만 Window 는 아니다.
+ if (!app.Dispatcher.CheckAccess())
+ {
+ return app.Dispatcher.Invoke(
+ () => Ask(kind, caption, message, detail, buttons, yes, no, destructive, owner));
+ }
+
+ var dialog = new Views.MessageDialogView(kind, caption, message, detail, buttons, yes, no, destructive);
+
+ // ③ 소유 창은 '살아 있는 것'만 건다. 아직 보이지 않았거나 이미 닫힌 창을 Owner 로 주면
+ // WPF 가 예외를 던진다 — 진단 렌더러가 도는 동안 MainWindow 가 닫힌 창을 가리킬 수 있다.
+ var target = owner ?? app.MainWindow;
+ if (target is { IsLoaded: true, IsVisible: true } && !ReferenceEquals(target, dialog))
+ {
+ dialog.Owner = target;
+ dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
+ }
+ else
+ {
+ // 소유 창이 없으면 화면 가운데에 띄우고 작업 표시줄에도 노출한다 —
+ // 안 그러면 다른 창 뒤로 숨어 '앱이 멈춘' 것처럼 보인다
+ dialog.WindowStartupLocation = WindowStartupLocation.CenterScreen;
+ dialog.ShowInTaskbar = true;
+ }
+
+ dialog.ShowDialog();
+ return dialog.Answer;
+ }
+
+ /// 창을 띄우지 못했을 때의 안전 기본값 — MessageBox 의 기각 의미론과 같다
+ private static bool? DismissValue(DialogButtons buttons) => buttons switch
+ {
+ DialogButtons.YesNo => false,
+ DialogButtons.YesNoCancel => null,
+ _ => true,
+ };
#endregion
}
diff --git a/src/SheetMe.Designer/Themes/DesignerTheme.xaml b/src/SheetMe.Designer/Themes/DesignerTheme.xaml
index 46a6562..19e94b1 100644
--- a/src/SheetMe.Designer/Themes/DesignerTheme.xaml
+++ b/src/SheetMe.Designer/Themes/DesignerTheme.xaml
@@ -80,12 +80,48 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SheetMe.Designer/Themes/Tokens.Dark.xaml b/src/SheetMe.Designer/Themes/Tokens.Dark.xaml
index 6173dc3..fb5603c 100644
--- a/src/SheetMe.Designer/Themes/Tokens.Dark.xaml
+++ b/src/SheetMe.Designer/Themes/Tokens.Dark.xaml
@@ -43,6 +43,14 @@
+
+
+
+
+
+
diff --git a/src/SheetMe.Designer/Themes/Tokens.Light.xaml b/src/SheetMe.Designer/Themes/Tokens.Light.xaml
index 0091d4a..157648e 100644
--- a/src/SheetMe.Designer/Themes/Tokens.Light.xaml
+++ b/src/SheetMe.Designer/Themes/Tokens.Light.xaml
@@ -73,6 +73,17 @@
+
+
+
+
+
+
diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
index b6ecb7b..267d5da 100644
--- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
@@ -448,8 +448,8 @@ public sealed class DesignerViewModel : ViewModelBase
{
return;
}
- if (MessageBox.Show($"페이지 {target.Index + 1}을(를) 삭제할까요? (컨트롤 {target.Controls.Count}개 포함)",
- "페이지 삭제", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
+ if (!DialogService.Confirm("페이지 삭제", $"페이지 {target.Index + 1}을(를) 삭제할까요?",
+ $"컨트롤 {target.Controls.Count}개가 함께 삭제됩니다.", yes: "삭제", no: "취소", destructive: true))
{
return;
}
@@ -465,8 +465,8 @@ public sealed class DesignerViewModel : ViewModelBase
{
return;
}
- if (MessageBox.Show($"페이지 {SelectedPage.Index + 1}을(를) 삭제할까요? (컨트롤 {SelectedPage.Controls.Count}개 포함)",
- "페이지 삭제", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
+ if (!DialogService.Confirm("페이지 삭제", $"페이지 {SelectedPage.Index + 1}을(를) 삭제할까요?",
+ $"컨트롤 {SelectedPage.Controls.Count}개가 함께 삭제됩니다.", yes: "삭제", no: "취소", destructive: true))
{
return;
}
@@ -1117,12 +1117,11 @@ public sealed class DesignerViewModel : ViewModelBase
var (pastable, blocked) = FilterPastable(clipboard);
if (blocked > 0)
{
- System.Windows.MessageBox.Show(
- $"표(Spread) {blocked}개는 붙여넣지 않았습니다.\n\n" +
+ Services.DialogService.Notify(Services.DialogKind.Warning, "붙여넣기",
+ $"표(Spread) {blocked}개는 붙여넣지 않았습니다.",
"표의 격자 디자인은 서식코드+컨트롤이름으로 별도 테이블(E_SpdMst)에서 조회됩니다.\n" +
"복제본에는 격자가 없어 EMR 에서 해당 서식이 열리지 않습니다.\n" +
- "표 추가는 레거시 서식생성기를 사용하세요.",
- "붙여넣기", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
+ "표 추가는 레거시 서식생성기를 사용하세요.");
}
if (pastable.Count == 0)
{
diff --git a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
index 39736d7..a0281a4 100644
--- a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs
@@ -341,7 +341,7 @@ public sealed class InspectorViewModel : ViewModelBase
{
if (target.Model.Props.Contains(key))
{
- System.Windows.MessageBox.Show($"이미 존재하는 속성입니다: {key}", "속성 추가");
+ Services.DialogService.Notify(Services.DialogKind.Warning, "속성 추가", $"이미 존재하는 속성입니다: {key}");
return;
}
designer.Undo.Snapshot();
@@ -609,11 +609,10 @@ public sealed class InspectorViewModel : ViewModelBase
// 최상위로 전파되어 그 서식이 통째로 열리지 않는다. SheetMe 는 E_SpdMst 행을 만들 수 없으므로 금지한다.
if (DesignerViewModel.IsSpread(vm.Model))
{
- System.Windows.MessageBox.Show(
- "표(Spread)는 이름을 바꿀 수 없습니다.\n\n" +
+ Services.DialogService.Notify(Services.DialogKind.Warning, "이름 변경",
+ "표(Spread)는 이름을 바꿀 수 없습니다.",
$"격자 디자인이 서식코드+이름('{vm.Id}')으로 별도 테이블(E_SpdMst)에 저장되어 있어,\n" +
- "이름을 바꾸면 EMR 에서 이 서식이 열리지 않습니다.",
- "이름 변경", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Warning);
+ "이름을 바꾸면 EMR 에서 이 서식이 열리지 않습니다.");
Rebuild();
return;
}
@@ -622,7 +621,7 @@ public sealed class InspectorViewModel : ViewModelBase
used.Remove(vm.Id);
if (used.Contains(trimmed))
{
- System.Windows.MessageBox.Show($"이미 사용 중인 이름입니다: {trimmed}", "이름 변경");
+ Services.DialogService.Notify(Services.DialogKind.Warning, "이름 변경", $"이미 사용 중인 이름입니다: {trimmed}");
// 거부했으므로 편집 상자에 남은 잘못된 이름을 원래 값으로 되돌린다
Rebuild();
return;
diff --git a/src/SheetMe.Designer/ViewModels/MainViewModel.cs b/src/SheetMe.Designer/ViewModels/MainViewModel.cs
index e538111..b423248 100644
--- a/src/SheetMe.Designer/ViewModels/MainViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/MainViewModel.cs
@@ -308,8 +308,9 @@ internal sealed class MainViewModel : ViewModelBase
return;
}
if (designer.Undo.IsDirty
- && MessageBox.Show($"'{designer.DisplayName}' 문서에 저장하지 않은 변경이 있습니다.\n닫을까요?",
- "문서 닫기", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
+ && !DialogService.Confirm("문서 닫기",
+ $"'{designer.DisplayName}' 문서에 저장하지 않은 변경이 있습니다.",
+ "닫을까요?", yes: "닫기", no: "취소", destructive: true))
{
return;
}
@@ -341,24 +342,23 @@ internal sealed class MainViewModel : ViewModelBase
// 과거 버전 열람 탭은 저장하면 활성 디자인을 과거 내용으로 덮게 되므로 저장 선택지를 주지 않는다
if (designer.HistorySdgKey is not null)
{
- var discard = MessageBox.Show(
- $"'{designer.DisplayName}' 은(는) 과거 버전 열람 탭이라 저장할 수 없습니다.\n변경을 버리고 종료할까요?",
- "종료", MessageBoxButton.YesNo, MessageBoxImage.Warning);
- if (discard != MessageBoxResult.Yes)
+ if (!DialogService.Confirm("종료",
+ $"'{designer.DisplayName}' 은(는) 과거 버전 열람 탭이라 저장할 수 없습니다.",
+ "변경을 버리고 종료할까요?", yes: "버리고 종료", no: "취소", destructive: true))
{
return false;
}
continue;
}
- var answer = MessageBox.Show(
- $"'{designer.DisplayName}' 문서에 저장하지 않은 변경이 있습니다.\n저장할까요?",
- "종료", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);
- if (answer == MessageBoxResult.Cancel)
+ var answer = DialogService.ConfirmWithCancel("종료",
+ $"'{designer.DisplayName}' 문서에 저장하지 않은 변경이 있습니다.",
+ "저장할까요?", yes: "저장", no: "저장 안 함");
+ if (answer is null)
{
return false;
}
- if (answer == MessageBoxResult.No)
+ if (answer == false)
{
continue;
}
@@ -390,20 +390,19 @@ internal sealed class MainViewModel : ViewModelBase
case SheetDesignPermission.Allowed:
return true;
case SheetDesignPermission.NotRegistered:
- MessageBox.Show($"기록지 정보에 등록되지 않은 서식 코드입니다. ({shtCod})", "확인",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "확인",
+ $"기록지 정보에 등록되지 않은 서식 코드입니다. ({shtCod})");
return false;
default:
Services.AppLog.Audit($"[권한거부] {shtCod} — ShtUsrDesYon 차단 (by {dataBusiness.User.Display})");
- MessageBox.Show("서식생성기를 사용하지 않는 서식지입니다.\n기록지 정보를 확인해주세요.", "확인",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "확인",
+ "서식생성기를 사용하지 않는 서식지입니다.", "기록지 정보를 확인해주세요.");
return false;
}
}
catch (Exception ex)
{
- MessageBox.Show($"서식 권한을 확인하지 못했습니다.\n\n{ex.Message}", "확인",
- MessageBoxButton.OK, MessageBoxImage.Warning);
+ DialogService.Notify(DialogKind.Warning, "확인", "서식 권한을 확인하지 못했습니다.", ex.Message);
return false;
}
}
@@ -497,8 +496,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"저장 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "저장 중 오류가 발생했습니다.", ex.Message);
return false;
}
}
@@ -536,8 +534,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"DB에서 서식을 여는 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "DB에서 서식을 여는 중 오류가 발생했습니다.", ex.Message);
}
}
@@ -564,8 +561,7 @@ internal sealed class MainViewModel : ViewModelBase
var document = dataBusiness.OpenFromDb(shtCod);
if (document is null)
{
- MessageBox.Show("활성 디자인을 찾지 못했습니다.", "DB 열기",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "DB 열기", "활성 디자인을 찾지 못했습니다.");
return;
}
@@ -586,16 +582,18 @@ internal sealed class MainViewModel : ViewModelBase
}
if (!dataBusiness.CanSaveToDb)
{
- MessageBox.Show("DB 저장이 비활성화되어 있습니다.\nappsettings.json 의 FormStore:SaveMode 를 'Db' 로 설정한 뒤 사용하세요.\n(운영 레거시 테이블에 기록되므로 명시적 활성화가 필요합니다)",
- "DB 저장", MessageBoxButton.OK, MessageBoxImage.Warning);
+ DialogService.Notify(DialogKind.Warning, "DB 저장",
+ "DB 저장이 비활성화되어 있습니다.",
+ "appsettings.json 의 FormStore:SaveMode 를 'Db' 로 설정한 뒤 사용하세요.\n" +
+ "(운영 레거시 테이블에 기록되므로 명시적 활성화가 필요합니다)");
return false;
}
if (!dataBusiness.CanWriteDb)
{
- MessageBox.Show("HIS 사용자가 확인되지 않아 DB에 저장할 수 없습니다.\n" +
+ DialogService.Notify(DialogKind.Warning, "DB 저장",
+ "HIS 사용자가 확인되지 않아 DB에 저장할 수 없습니다.",
"기록지정보 화면에서 서식생성기를 실행하거나, 사용자 코드를 인자로 전달해 주세요.\n" +
- "(저장 이력에 남길 수정자를 특정할 수 없습니다)",
- "DB 저장", MessageBoxButton.OK, MessageBoxImage.Warning);
+ "(저장 이력에 남길 수정자를 특정할 수 없습니다)");
return false;
}
CommitPendingEdits();
@@ -627,12 +625,12 @@ internal sealed class MainViewModel : ViewModelBase
document.Title = register.SheetName;
}
- var confirm = MessageBox.Show(
- $"서식 [{document.FormId}] {document.Title} 을(를) DB(E_SdgMst/E_SctMst)에 저장할까요?\n\n" +
+ var confirm = DialogService.Confirm("DB 저장 확인",
+ $"서식 [{document.FormId}] {document.Title} 을(를) DB(E_SdgMst/E_SctMst)에 저장할까요?",
"기존 활성 디자인은 이력(SdgDelYon='Y')으로 보존되고 새 버전이 생성됩니다.\n" +
"(제자리 갱신 서식(ShtCneYon='Y')은 기존 버전이 갱신됩니다)",
- "DB 저장 확인", MessageBoxButton.YesNo, MessageBoxImage.Question);
- if (confirm != MessageBoxResult.Yes)
+ yes: "저장", no: "취소");
+ if (!confirm)
{
return false;
}
@@ -643,14 +641,14 @@ internal sealed class MainViewModel : ViewModelBase
CurrentDesigner.Undo.MarkSaved();
CurrentDesigner.NotifyDisplayNameChanged();
StatusText = $"DB 저장 완료: {document.FormId} → SdgKey {sdgKey}";
- MessageBox.Show($"저장되었습니다. (SdgKey {sdgKey})\n레거시 뷰어/디자이너에서 열어 확인하세요.", "DB 저장",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Info, "DB 저장",
+ $"저장되었습니다. (SdgKey {sdgKey})", "레거시 뷰어/디자이너에서 열어 확인하세요.");
return true;
}
catch (Exception ex)
{
- MessageBox.Show($"DB 저장 중 오류가 발생했습니다. (트랜잭션 롤백됨)\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류",
+ "DB 저장 중 오류가 발생했습니다. (트랜잭션 롤백됨)", ex.Message);
return false;
}
}
@@ -675,8 +673,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"JSON 내보내기 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "JSON 내보내기 중 오류가 발생했습니다.", ex.Message);
}
}
@@ -699,8 +696,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"JSON 가져오기 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "JSON 가져오기 중 오류가 발생했습니다.", ex.Message);
}
}
#endregion
@@ -743,8 +739,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"서식 목록 조회 중 오류가 발생했습니다.\n\n{ex.Message}", "서식 목록",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "서식 목록", "서식 목록 조회 중 오류가 발생했습니다.", ex.Message);
}
finally
{
@@ -765,16 +760,14 @@ internal sealed class MainViewModel : ViewModelBase
}
if (!sheet.HasDesign)
{
- MessageBox.Show("선택한 서식에는 저장된 디자인이 없습니다.", "서식 열기",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "서식 열기", "선택한 서식에는 저장된 디자인이 없습니다.");
return;
}
OpenDbSheet(sheet.ShtCod);
}
catch (Exception ex)
{
- MessageBox.Show($"서식을 여는 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "서식을 여는 중 오류가 발생했습니다.", ex.Message);
}
}
#endregion
@@ -806,8 +799,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"인쇄 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "인쇄 중 오류가 발생했습니다.", ex.Message);
}
}
@@ -834,8 +826,7 @@ internal sealed class MainViewModel : ViewModelBase
var document = CurrentDesigner.Document;
if (document.FormId.Length == 0 || document.FormId == "NewSheet")
{
- MessageBox.Show("수정이력은 DB에 저장된 서식에서 사용할 수 있습니다.", "서식 수정이력",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "서식 수정이력", "수정이력은 DB에 저장된 서식에서 사용할 수 있습니다.");
return;
}
@@ -849,8 +840,7 @@ internal sealed class MainViewModel : ViewModelBase
var versions = dataBusiness.ListVersions(document.FormId);
if (versions.Count == 0)
{
- MessageBox.Show("저장된 버전이 없습니다.", "서식 수정이력",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Info, "서식 수정이력", "저장된 버전이 없습니다.");
return;
}
@@ -875,8 +865,7 @@ internal sealed class MainViewModel : ViewModelBase
var versionDocument = dataBusiness.OpenFromDbVersion(document.FormId, sdgKey);
if (versionDocument is null)
{
- MessageBox.Show("해당 버전을 불러오지 못했습니다.", "서식 수정이력",
- MessageBoxButton.OK, MessageBoxImage.Warning);
+ DialogService.Notify(DialogKind.Warning, "서식 수정이력", "해당 버전을 불러오지 못했습니다.");
return;
}
@@ -889,8 +878,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"수정이력 조회 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "수정이력 조회 중 오류가 발생했습니다.", ex.Message);
}
}
@@ -909,8 +897,8 @@ internal sealed class MainViewModel : ViewModelBase
var document = CurrentDesigner.Document;
if (document.FormId.Length == 0 || document.FormId == "NewSheet")
{
- MessageBox.Show("상용구는 서식 코드 단위로 저장됩니다.\n먼저 DB에 저장(서식 등록)한 뒤 사용하세요.",
- "상용구 관리", MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "상용구 관리",
+ "상용구는 서식 코드 단위로 저장됩니다.", "먼저 DB에 저장(서식 등록)한 뒤 사용하세요.");
return;
}
@@ -922,8 +910,7 @@ internal sealed class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
- MessageBox.Show($"상용구 관리 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "상용구 관리 중 오류가 발생했습니다.", ex.Message);
}
}
#endregion
@@ -935,8 +922,8 @@ internal sealed class MainViewModel : ViewModelBase
{
return true;
}
- MessageBox.Show("DB 접속 문자열(ConnectionStrings:His)이 설정되지 않았습니다.\nappsettings.json 을 확인하세요.",
- "DB 연결", MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "DB 연결",
+ "DB 접속 문자열(ConnectionStrings:His)이 설정되지 않았습니다.", "appsettings.json 을 확인하세요.");
return false;
}
@@ -948,8 +935,8 @@ internal sealed class MainViewModel : ViewModelBase
}
var summary = string.Join("\n", warnings.Take(20));
var more = warnings.Count > 20 ? $"\n... 외 {warnings.Count - 20}건" : string.Empty;
- MessageBox.Show($"읽기 경고 {warnings.Count}건 (미지원 컨트롤은 자리표시로 보존됩니다):\n\n{summary}{more}",
- caption, MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, caption,
+ $"읽기 경고 {warnings.Count}건 (미지원 컨트롤은 자리표시로 보존됩니다)", summary + more);
}
private static int CountControls(List controls)
diff --git a/src/SheetMe.Designer/Views/FontManagerDialogView.xaml.cs b/src/SheetMe.Designer/Views/FontManagerDialogView.xaml.cs
index 0dcceb6..7aadddb 100644
--- a/src/SheetMe.Designer/Views/FontManagerDialogView.xaml.cs
+++ b/src/SheetMe.Designer/Views/FontManagerDialogView.xaml.cs
@@ -4,6 +4,8 @@ using System.Windows.Media;
using SheetMe.Core.Serialization;
using SheetMe.Designer.ViewModels;
+using SheetMe.Designer.Services;
+
namespace SheetMe.Designer.Views;
///
@@ -47,19 +49,19 @@ public partial class FontManagerDialogView : Window
{
if (targets.Count == 0)
{
- MessageBox.Show("변경할 조합을 선택하세요.", "폰트 일괄 변경");
+ DialogService.Notify(DialogKind.Warning, "폰트 일괄 변경", "변경할 조합을 선택하세요.", owner: this);
return;
}
var family = FamilyBox.Text.Trim();
if (family.Length == 0)
{
- MessageBox.Show("글꼴명을 입력하세요.", "폰트 일괄 변경");
+ DialogService.Notify(DialogKind.Warning, "폰트 일괄 변경", "글꼴명을 입력하세요.", owner: this);
return;
}
if (!double.TryParse(SizeBox.Text.Trim(), NumberStyles.Number, CultureInfo.InvariantCulture, out var sizePt)
|| sizePt <= 0 || sizePt > 200)
{
- MessageBox.Show("크기(pt)를 올바르게 입력하세요.", "폰트 일괄 변경");
+ DialogService.Notify(DialogKind.Warning, "폰트 일괄 변경", "크기(pt)를 올바르게 입력하세요.", owner: this);
return;
}
diff --git a/src/SheetMe.Designer/Views/MessageDialogView.xaml b/src/SheetMe.Designer/Views/MessageDialogView.xaml
new file mode 100644
index 0000000..b3a0502
--- /dev/null
+++ b/src/SheetMe.Designer/Views/MessageDialogView.xaml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SheetMe.Designer/Views/MessageDialogView.xaml.cs b/src/SheetMe.Designer/Views/MessageDialogView.xaml.cs
new file mode 100644
index 0000000..95644e9
--- /dev/null
+++ b/src/SheetMe.Designer/Views/MessageDialogView.xaml.cs
@@ -0,0 +1,161 @@
+using System.Windows;
+using System.Windows.Input;
+using SheetMe.Designer.Controls;
+using SheetMe.Designer.Services;
+
+namespace SheetMe.Designer.Views;
+
+///
+/// 알림·확인 대화상자 — 순정 Win32 MessageBox 대체.
+///
+/// 기각 의미론은 MessageBox 를 그대로 따른다: 닫기(X)·Alt+F4·Esc 는
+/// 확인 1개면 '확인', 예/아니오면 '아니오', 예/아니오/취소면 '취소'로 돌아간다.
+///
+/// 를 쓰지 않는다. Show() 로 띄운 창에 대입하면
+/// InvalidOperationException 이고, 진단 렌더러(DialogShots)가 정확히 그 경로로 이 창을 찍는다.
+/// 같은 이유로 Closing 에서 e.Cancel 로 닫기를 막지도 않는다 —
+/// 렌더러가 Close() 로만 창을 정리하므로 취소되면 화면 밖 창이 살아남는다.
+///
+public partial class MessageDialogView : Window
+{
+ #region Member Fields
+ private readonly string caption;
+ private readonly string head;
+ private readonly string? detail;
+ private bool? answer;
+ #endregion
+
+ #region Properties
+ /// 사용자 응답 — 예=true / 아니오=false / 취소=null
+ public bool? Answer => answer;
+ #endregion
+
+ #region Constructors
+ ///
+ /// 공개 생성자 — 정적 헬퍼로 감싸지 말고 이 형태를 유지할 것.
+ /// 진단 렌더러가 팩토리로 new 해서 라이트·다크 스냅샷을 찍는다(private 생성자면 편입 불가).
+ ///
+ public MessageDialogView(DialogKind kind, string caption, string head, string? detail,
+ DialogButtons buttons, string yes = "예", string no = "아니오", bool destructive = false)
+ {
+ InitializeComponent();
+
+ this.caption = caption;
+ this.head = head;
+ this.detail = detail;
+
+ // 노트북 해상도에서 창이 화면을 덮지 않도록 한 번 더 조인다
+ MaxHeight = Math.Min(620, SystemParameters.WorkArea.Height * 0.85);
+
+ CaptionText.Text = caption;
+ HeadText.Text = head;
+ DetailText.Text = detail ?? string.Empty;
+ DetailText.Visibility = string.IsNullOrEmpty(detail) ? Visibility.Collapsed : Visibility.Visible;
+
+ var (icon, brushKey) = Visual(kind);
+ // 색은 반드시 '키'로 넘긴다 — Brush 인스턴스를 대입하면 그 시점 값으로 굳어 테마 전환을 못 따라간다
+ SeverityIcon.Content = LucideIcons.Icon(icon, 22, brushKey);
+ // 오류만 복사 버튼을 노출한다 — 오류 코드를 전화로 불러 주던 실사용 경로
+ CopyButton.Visibility = kind == DialogKind.Error ? Visibility.Visible : Visibility.Collapsed;
+
+ Configure(buttons, yes, no, destructive);
+ }
+ #endregion
+
+ #region Methods
+ /// 성격 → (아이콘, 색 토큰)
+ private static (string Icon, string BrushKey) Visual(DialogKind kind) => kind switch
+ {
+ DialogKind.Error => ("circle-alert", "B.Danger"),
+ DialogKind.Warning => ("triangle-alert", "B.Warning"),
+ DialogKind.Question => ("circle-help", "B.AccentText"),
+ _ => ("info", "B.AccentText"),
+ };
+
+ /// 버튼 구성·기본 응답·초기 포커스
+ private void Configure(DialogButtons buttons, string yes, string no, bool destructive)
+ {
+ switch (buttons)
+ {
+ case DialogButtons.YesNo:
+ answer = false;
+ AffirmButton.Content = yes;
+ DenyButton.Content = no;
+ DenyButton.Visibility = Visibility.Visible;
+ DenyButton.IsCancel = true;
+ break;
+
+ case DialogButtons.YesNoCancel:
+ answer = null;
+ AffirmButton.Content = yes;
+ DenyButton.Content = no;
+ DenyButton.Visibility = Visibility.Visible;
+ CancelButton.Visibility = Visibility.Visible;
+ CancelButton.IsCancel = true;
+ break;
+
+ default:
+ answer = true;
+ AffirmButton.Content = "확인";
+ AffirmButton.IsCancel = true;
+ break;
+ }
+
+ // 되돌리기 어려운 확인에서는 기본 버튼을 부정 쪽에 둔다 — Enter 한 번에 페이지가 지워지면 안 된다
+ var defaultButton = destructive && DenyButton.Visibility == Visibility.Visible
+ ? DenyButton
+ : AffirmButton;
+ defaultButton.IsDefault = true;
+ Loaded += (_, _) => defaultButton.Focus();
+ }
+
+ private void OnAffirmClick(object sender, RoutedEventArgs e)
+ {
+ answer = true;
+ Close();
+ }
+
+ private void OnDenyClick(object sender, RoutedEventArgs e)
+ {
+ answer = false;
+ Close();
+ }
+
+ /// 닫기(X)·취소 — 생성자에서 정한 기각 기본값을 그대로 두고 닫는다
+ private void OnDismissClick(object sender, RoutedEventArgs e) => Close();
+
+ /// 본문이 넘칠 때만 버튼 줄 위에 구분선 — 창이 최대 높이에 걸렸다는 유일한 신호
+ private void OnBodyScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
+ => Footer.BorderThickness = new Thickness(0, BodyScroll.ScrollableHeight > 0.5 ? 1 : 0, 0, 0);
+
+ /// 내용 복사 — MessageBox 의 Ctrl+C 동작 보존(오류 코드를 옮겨 적던 경로)
+ private void OnCopyClick(object sender, RoutedEventArgs e) => CopyToClipboard();
+
+ protected override void OnKeyDown(KeyEventArgs e)
+ {
+ if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
+ {
+ CopyToClipboard();
+ e.Handled = true;
+ return;
+ }
+ base.OnKeyDown(e);
+ }
+
+ private void CopyToClipboard()
+ {
+ var text = string.IsNullOrEmpty(detail)
+ ? $"{caption}\n---\n{head}"
+ : $"{caption}\n---\n{head}\n\n{detail}";
+ try
+ {
+ Clipboard.SetText(text);
+ }
+ catch (Exception ex)
+ {
+ // 클립보드는 다른 프로세스가 잠글 수 있다 — 복사 실패가 알림 자체를 막으면 안 된다
+ AppLog.Warn($"알림 내용 복사 실패: {ex.Message}");
+ }
+ }
+ #endregion
+}
diff --git a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
index 1d09d54..088a7d5 100644
--- a/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
+++ b/src/SheetMe.Designer/Views/PreviewWindow.xaml.cs
@@ -53,8 +53,7 @@ public partial class PreviewWindow : Window
}
catch (Exception ex)
{
- MessageBox.Show($"인쇄 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "인쇄 중 오류가 발생했습니다.", ex.Message, this);
}
}
diff --git a/src/SheetMe.Designer/Views/RecordWordDialogView.xaml.cs b/src/SheetMe.Designer/Views/RecordWordDialogView.xaml.cs
index cf22c4f..79e5901 100644
--- a/src/SheetMe.Designer/Views/RecordWordDialogView.xaml.cs
+++ b/src/SheetMe.Designer/Views/RecordWordDialogView.xaml.cs
@@ -2,6 +2,8 @@ using System.Collections.ObjectModel;
using System.Windows;
using SheetMe.Data.Stores;
+using SheetMe.Designer.Services;
+
namespace SheetMe.Designer.Views;
///
@@ -45,9 +47,9 @@ public partial class RecordWordDialogView : Window
{
return true;
}
- MessageBox.Show("HIS 사용자가 확인되지 않아 상용구를 변경할 수 없습니다.\n" +
- "기록지정보 화면에서 서식생성기를 실행해 주세요.",
- "상용구 관리", MessageBoxButton.OK, MessageBoxImage.Warning);
+ DialogService.Notify(DialogKind.Warning, "상용구 관리",
+ "HIS 사용자가 확인되지 않아 상용구를 변경할 수 없습니다.",
+ "기록지정보 화면에서 서식생성기를 실행해 주세요.", this);
return false;
}
#endregion
@@ -65,8 +67,7 @@ public partial class RecordWordDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"상용구 조회 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "상용구 조회 중 오류가 발생했습니다.", ex.Message, this);
}
}
@@ -87,8 +88,7 @@ public partial class RecordWordDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"추가 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "추가 중 오류가 발생했습니다.", ex.Message, this);
}
}
@@ -116,8 +116,7 @@ public partial class RecordWordDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"수정 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "수정 중 오류가 발생했습니다.", ex.Message, this);
}
}
@@ -127,8 +126,8 @@ public partial class RecordWordDialogView : Window
{
return;
}
- if (MessageBox.Show($"삭제할까요?\n\n{Truncate(selected.Value)}", "상용구 삭제",
- MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
+ if (!DialogService.Confirm("상용구 삭제", "삭제할까요?", Truncate(selected.Value),
+ yes: "삭제", no: "취소", destructive: true, owner: this))
{
return;
}
@@ -139,8 +138,7 @@ public partial class RecordWordDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"삭제 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "삭제 중 오류가 발생했습니다.", ex.Message, this);
}
}
@@ -164,8 +162,7 @@ public partial class RecordWordDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"순서 변경 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "순서 변경 중 오류가 발생했습니다.", ex.Message, this);
Reload();
}
}
diff --git a/src/SheetMe.Designer/Views/RegisterSheetDialogView.xaml.cs b/src/SheetMe.Designer/Views/RegisterSheetDialogView.xaml.cs
index 70fa8c1..aabeaa5 100644
--- a/src/SheetMe.Designer/Views/RegisterSheetDialogView.xaml.cs
+++ b/src/SheetMe.Designer/Views/RegisterSheetDialogView.xaml.cs
@@ -1,4 +1,5 @@
using System.Windows;
+using SheetMe.Designer.Services;
namespace SheetMe.Designer.Views;
@@ -30,7 +31,7 @@ public partial class RegisterSheetDialogView : Window
{
if (SheetCode.Length == 0 || SheetName.Length == 0)
{
- MessageBox.Show("서식 코드와 명칭을 입력하세요.", "신규 서식 등록");
+ DialogService.Notify(DialogKind.Warning, "신규 서식 등록", "서식 코드와 명칭을 입력하세요.", owner: this);
return;
}
DialogResult = true;
diff --git a/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml.cs b/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml.cs
index 388b353..f718b22 100644
--- a/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml.cs
+++ b/src/SheetMe.Designer/Views/SheetOpenDialogView.xaml.cs
@@ -1,6 +1,8 @@
using System.Windows;
using SheetMe.Data.Stores;
+using SheetMe.Designer.Services;
+
namespace SheetMe.Designer.Views;
///
@@ -40,8 +42,7 @@ public partial class SheetOpenDialogView : Window
}
catch (Exception ex)
{
- MessageBox.Show($"서식 목록 조회 중 오류가 발생했습니다.\n\n{ex.Message}", "오류",
- MessageBoxButton.OK, MessageBoxImage.Error);
+ DialogService.Notify(DialogKind.Error, "오류", "서식 목록 조회 중 오류가 발생했습니다.", ex.Message, this);
}
}
@@ -53,8 +54,7 @@ public partial class SheetOpenDialogView : Window
}
if (!selected.HasDesign)
{
- MessageBox.Show("선택한 서식에는 저장된 디자인이 없습니다.", "열기",
- MessageBoxButton.OK, MessageBoxImage.Information);
+ DialogService.Notify(DialogKind.Warning, "열기", "선택한 서식에는 저장된 디자인이 없습니다.", owner: this);
return;
}
SelectedSheet = selected;