버튼 이름 변경 및 Windows 종료/재시작 숫자 확인 추가
- 선택 Start/선택 Stop 버튼을 Start/Stop으로 변경 - Windows 강제종료/재시작 시 무작위 숫자 6자리를 입력해야만 실행 (붙여넣기 불가) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013U497kUNL8ArhV7jkzCpKamain
parent
84cafc48a2
commit
bb34662582
|
|
@ -16,8 +16,8 @@ Windows 서비스 기동/종료 관리 프로그램 (C# WPF, .NET 8)
|
||||||
### 초기화면
|
### 초기화면
|
||||||
- **추가**: Windows 서비스 전체 목록에서 관리할 서비스 선택
|
- **추가**: Windows 서비스 전체 목록에서 관리할 서비스 선택
|
||||||
- **삭제**: 체크한 서비스를 목록에서 제외 (Windows 서비스 자체는 건드리지 않음)
|
- **삭제**: 체크한 서비스를 목록에서 제외 (Windows 서비스 자체는 건드리지 않음)
|
||||||
- **선택 Start / 선택 Stop**: 체크한 서비스를 차례로 시작/중지
|
- **Start / Stop**: 체크한 서비스를 차례로 시작/중지
|
||||||
- **Windows 강제종료 / Windows 재시작**: 확인 후 즉시 실행 (설정에서 버튼 숨김 가능)
|
- **Windows 강제종료 / Windows 재시작**: 확인창에 표시된 무작위 숫자 6자리를 그대로 입력해야만 실행 (붙여넣기 불가, 설정에서 버튼 숨김 가능)
|
||||||
- **설정**
|
- **설정**
|
||||||
- 서비스마다 **Start / Stop / Status / Log** 버튼, 상태는 2초마다 자동 갱신 (초록=실행 중, 빨강=중지됨)
|
- 서비스마다 **Start / Stop / Status / Log** 버튼, 상태는 2초마다 자동 갱신 (초록=실행 중, 빨강=중지됨)
|
||||||
- 서비스 줄 **우클릭**: 로그 파일 지정 / 로그 폴더 지정 / 경로 해제
|
- 서비스 줄 **우클릭**: 로그 파일 지정 / 로그 폴더 지정 / 경로 해제
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
<Button x:Name="btnAdd" Content="추가" ToolTip="관리할 Windows 서비스를 선택합니다."/>
|
<Button x:Name="btnAdd" Content="추가" ToolTip="관리할 Windows 서비스를 선택합니다."/>
|
||||||
<Button x:Name="btnDelete" Content="삭제" ToolTip="체크한 서비스를 목록에서 뺍니다. (Windows 서비스는 그대로 유지)"/>
|
<Button x:Name="btnDelete" Content="삭제" ToolTip="체크한 서비스를 목록에서 뺍니다. (Windows 서비스는 그대로 유지)"/>
|
||||||
<Rectangle Width="1" Fill="#CFD8DC" Margin="8,4"/>
|
<Rectangle Width="1" Fill="#CFD8DC" Margin="8,4"/>
|
||||||
<Button x:Name="btnStartChecked" Content="선택 Start" ToolTip="체크한 서비스를 차례로 시작합니다."/>
|
<Button x:Name="btnStartChecked" Content="Start" ToolTip="체크한 서비스를 차례로 시작합니다."/>
|
||||||
<Button x:Name="btnStopChecked" Content="선택 Stop" ToolTip="체크한 서비스를 차례로 중지합니다."/>
|
<Button x:Name="btnStopChecked" Content="Stop" ToolTip="체크한 서비스를 차례로 중지합니다."/>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace JJServiceManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 위험 작업 확인창: 무작위 숫자 6자리를 보여주고, 그 숫자를 정확히 입력해야만 확인됨
|
||||||
|
/// (Windows 강제종료 / Windows 재시작)
|
||||||
|
/// </summary>
|
||||||
|
public sealed class CodeConfirmWindow : Window
|
||||||
|
{
|
||||||
|
private readonly string _code;
|
||||||
|
private readonly TextBox _input;
|
||||||
|
private readonly Button _ok;
|
||||||
|
private readonly TextBlock _hint;
|
||||||
|
|
||||||
|
public CodeConfirmWindow(string title, string message, string actionText)
|
||||||
|
{
|
||||||
|
_code = RandomNumberGenerator.GetInt32(0, 1_000_000).ToString("D6");
|
||||||
|
|
||||||
|
Title = title;
|
||||||
|
Width = 440;
|
||||||
|
SizeToContent = SizeToContent.Height;
|
||||||
|
ResizeMode = ResizeMode.NoResize;
|
||||||
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
Topmost = true;
|
||||||
|
FontFamily = new FontFamily("Malgun Gothic");
|
||||||
|
FontSize = 13;
|
||||||
|
Background = Brushes2.Make("#FAFAFA");
|
||||||
|
AppIcon.Apply(this);
|
||||||
|
|
||||||
|
var root = new StackPanel { Margin = new Thickness(20, 18, 20, 16) };
|
||||||
|
|
||||||
|
// 경고 문구
|
||||||
|
root.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "⚠ " + message,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
Foreground = Brushes2.Make("#B71C1C"),
|
||||||
|
FontWeight = FontWeights.SemiBold,
|
||||||
|
Margin = new Thickness(0, 0, 0, 14)
|
||||||
|
});
|
||||||
|
|
||||||
|
root.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "계속하려면 아래 숫자 6자리를 그대로 입력하세요.",
|
||||||
|
Foreground = Brushes2.Make("#455A64"),
|
||||||
|
Margin = new Thickness(0, 0, 0, 8)
|
||||||
|
});
|
||||||
|
|
||||||
|
// 무작위 숫자 (복사할 수 없는 글자로 표시)
|
||||||
|
var codeBox = new Border
|
||||||
|
{
|
||||||
|
Background = Brushes2.Make("#FFF8E1"),
|
||||||
|
BorderBrush = Brushes2.Make("#FFB300"),
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(4),
|
||||||
|
Padding = new Thickness(0, 8, 0, 8),
|
||||||
|
Margin = new Thickness(0, 0, 0, 12),
|
||||||
|
Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = string.Join(" ", _code.ToCharArray()),
|
||||||
|
FontFamily = new FontFamily("Consolas"),
|
||||||
|
FontSize = 30,
|
||||||
|
FontWeight = FontWeights.Bold,
|
||||||
|
Foreground = Brushes2.Make("#E65100"),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
IsHitTestVisible = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
root.Children.Add(codeBox);
|
||||||
|
|
||||||
|
// 입력칸 (숫자만, 붙여넣기 불가)
|
||||||
|
_input = new TextBox
|
||||||
|
{
|
||||||
|
MaxLength = 6,
|
||||||
|
FontFamily = new FontFamily("Consolas"),
|
||||||
|
FontSize = 24,
|
||||||
|
Height = 42,
|
||||||
|
HorizontalContentAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalContentAlignment = VerticalAlignment.Center,
|
||||||
|
ToolTip = "위의 숫자 6자리를 입력"
|
||||||
|
};
|
||||||
|
InputMethod.SetIsInputMethodEnabled(_input, false);
|
||||||
|
_input.PreviewTextInput += (s, e) =>
|
||||||
|
{
|
||||||
|
foreach (var c in e.Text)
|
||||||
|
if (c < '0' || c > '9') { e.Handled = true; return; }
|
||||||
|
};
|
||||||
|
DataObject.AddPastingHandler(_input, (s, e) => e.CancelCommand());
|
||||||
|
_input.TextChanged += (s, e) => UpdateState();
|
||||||
|
root.Children.Add(_input);
|
||||||
|
|
||||||
|
_hint = new TextBlock
|
||||||
|
{
|
||||||
|
Text = " ",
|
||||||
|
Foreground = Brushes2.Make("#C62828"),
|
||||||
|
Margin = new Thickness(0, 6, 0, 0),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
root.Children.Add(_hint);
|
||||||
|
|
||||||
|
// 버튼
|
||||||
|
var buttons = new StackPanel
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Horizontal,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
Margin = new Thickness(0, 10, 0, 0)
|
||||||
|
};
|
||||||
|
_ok = new Button { Content = actionText, IsEnabled = false, MinWidth = 110 };
|
||||||
|
_ok.SetResourceReference(StyleProperty, "DangerButton");
|
||||||
|
_ok.Click += (s, e) => Submit();
|
||||||
|
var cancel = new Button { Content = "취소", MinWidth = 80, IsCancel = true };
|
||||||
|
buttons.Children.Add(_ok);
|
||||||
|
buttons.Children.Add(cancel);
|
||||||
|
root.Children.Add(buttons);
|
||||||
|
|
||||||
|
Content = root;
|
||||||
|
|
||||||
|
Loaded += (s, e) => _input.Focus();
|
||||||
|
PreviewKeyDown += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Enter)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateState()
|
||||||
|
{
|
||||||
|
var t = _input.Text;
|
||||||
|
_ok.IsEnabled = t == _code;
|
||||||
|
if (t.Length == 6 && t != _code) _hint.Text = "숫자가 다릅니다. 다시 입력하세요.";
|
||||||
|
else _hint.Text = " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Submit()
|
||||||
|
{
|
||||||
|
if (_input.Text == _code)
|
||||||
|
{
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_hint.Text = _input.Text.Length == 0 ? "숫자를 입력하세요." : "숫자가 다릅니다. 다시 입력하세요.";
|
||||||
|
_input.SelectAll();
|
||||||
|
_input.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>확인창을 띄우고, 숫자를 정확히 입력해 확인했으면 true</summary>
|
||||||
|
public static bool Ask(Window owner, string title, string message, string actionText)
|
||||||
|
{
|
||||||
|
var w = new CodeConfirmWindow(title, message, actionText);
|
||||||
|
if (owner != null && owner.IsVisible) w.Owner = owner;
|
||||||
|
else w.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||||
|
return w.ShowDialog() == true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -454,8 +454,21 @@ namespace JJServiceManager
|
||||||
private void Shutdown(bool restart)
|
private void Shutdown(bool restart)
|
||||||
{
|
{
|
||||||
var what = restart ? "재시작" : "강제 종료";
|
var what = restart ? "재시작" : "강제 종료";
|
||||||
if (!Dialogs.Confirm(this, $"Windows를 지금 {what}합니다.\n저장하지 않은 작업은 모두 사라집니다.\n\n계속할까요?", "Windows " + what))
|
if (_modalOpen) return;
|
||||||
|
bool confirmed;
|
||||||
|
_modalOpen = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
confirmed = CodeConfirmWindow.Ask(this, "Windows " + what,
|
||||||
|
$"Windows를 지금 {what}합니다.\n저장하지 않은 작업은 모두 사라집니다.",
|
||||||
|
restart ? "Windows 재시작" : "Windows 강제종료");
|
||||||
|
}
|
||||||
|
finally { _modalOpen = false; }
|
||||||
|
if (!confirmed)
|
||||||
|
{
|
||||||
|
Log($"Windows {what} 취소");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
Log($"Windows {what} 실행", LogLevel.Warning);
|
Log($"Windows {what} 실행", LogLevel.Warning);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue