반응형
1. 핵심 설계 포인트
- Audit Trail (감사 추적): 설정을 저장할 때 단순히 덮어쓰지 않고, "어떤 항목이 [기존 값]에서 [새로운 값]으로 바뀌었는지" 로그를 남겨 트러블 슈팅을 용이하게 합니다.
- DRY (Don't Repeat Yourself): UI 컨트롤에 값을 채워 넣는 코드를 SetControlsFromConfig 함수 하나로 통합하여, Form_Load와 Restore 버튼에서 재사용합니다.
- UX (사용자 경험): 중요한 변경이나 복원 전에는 반드시 MessageBox로 재확인을 거칩니다.
2. 전체 소스 코드
C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using A2_SOP_Linescan.Common;
using A2_SOP_Linescan.Managers;
namespace A2_SOP_Linescan.UI
{
public partial class FormSystemGeneral : Form
{
// ... (생성자 및 초기화 코드 생략)
#region [Event Handler] Save & Restore
/// <summary>
/// [Save] UI의 값을 메모리/파일에 저장하고, 변경된 내역을 로그로 남깁니다.
/// </summary>
private void btnSave_Click(object sender, EventArgs e)
{
try
{
// 1. 사용자 의사 확인
if (MessageBox.Show(new Form { TopMost = true },
"설정을 저장하시겠습니까?\n(변경된 내용은 로그에 기록됩니다.)",
"Save Configuration", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
{
return;
}
// 2. 변경 사항 감지 (Change Detection)
var changeLogData = new Dictionary<string, object>();
var config = GlobalDefine.SystemConfig; // 현재 메모리에 있는 설정값
// Helper 함수를 통해 [Old Value] -> [New Value] 비교
CheckChange(changeLogData, "EquipmentName", config.EquipmentName, txtEquipmentName.Text);
// Enum 및 Index 처리
StageType newStageType = (StageType)cmbEquipmentType.SelectedIndex;
CheckChange(changeLogData, "StageType", config.StageType.ToString(), newStageType.ToString());
int newLineIndex = cmbLineIndex.SelectedIndex + 1;
CheckChange(changeLogData, "ProductLine", config.ProductLine.ToString(), newLineIndex.ToString());
int newPcNumber = cmbEquipmentIndex.SelectedIndex + 1;
CheckChange(changeLogData, "PcNumber", config.PcNumber.ToString(), newPcNumber.ToString());
CheckChange(changeLogData, "SaveOrgImgPath", config.SaveOrgImgPath, txtSaveOrgImg.Text);
CheckChange(changeLogData, "SaveDetectImgPath", config.SaveDetectImgPath, txtSaveDetecImg.Text);
// 3. 실제 값 적용 (UI -> Memory)
config.EquipmentName = txtEquipmentName.Text;
config.StageType = newStageType;
config.ProductLine = newLineIndex;
config.PcNumber = newPcNumber;
config.SaveOrgImgPath = txtSaveOrgImg.Text;
config.SaveDetectImgPath = txtSaveDetecImg.Text;
// 4. 파일 저장 (Memory -> Disk)
string filePath = Path.Combine(GlobalDefine.Paths.System, GlobalDefine.Files.System);
SystemConfig.Save(filePath, config);
// 5. 로그 기록 (변경된 내용이 있을 때만 상세 기록)
if (changeLogData.Count > 0)
{
// LogManager가 Dictionary를 받아 "Key=[Old] -> [New]" 형태로 기록
LogManager.Instance.SettingChange("FormSystemGeneral", "System Configuration Saved", changeLogData);
}
else
{
LogManager.Instance.Init("FormSystemGeneral", "Configuration Saved (No Changes).");
}
MessageBox.Show("저장되었습니다.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
LogManager.Instance.Error("FormSystemGeneral", "Save Failed", ex);
MessageBox.Show($"저장 실패: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// [Restore] 저장되지 않은 변경 사항을 취소하고, 파일에 저장된 최신 상태로 되돌립니다.
/// </summary>
private void btnRestore_Click(object sender, EventArgs e)
{
try
{
if (MessageBox.Show(new Form { TopMost = true },
"모든 변경 사항을 취소하고 원래대로 되돌리시겠습니까?",
"Restore Configuration", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes)
{
return;
}
// 1. 설정 파일 다시 로드 (Disk -> Memory)
// 메모리상의 값도 오염되었을 수 있으므로 파일에서 새로 읽어옵니다.
string filePath = Path.Combine(GlobalDefine.Paths.System, GlobalDefine.Files.System);
var loadedConfig = SystemConfig.Load(filePath);
if (loadedConfig == null)
{
MessageBox.Show("설정 파일을 불러올 수 없습니다.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// 2. 전역 설정 객체 교체
GlobalDefine.SystemConfig = loadedConfig;
// 3. UI 갱신 (Memory -> UI)
// Form_Load 등에서도 쓰이는 공통 함수 호출
SetControlsFromConfig(GlobalDefine.SystemConfig);
// 4. 로그 기록
LogManager.Instance.Init("FormSystemGeneral", "System Configuration Restored from file.");
MessageBox.Show("설정이 복원되었습니다.", "Restored", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
LogManager.Instance.Error("FormSystemGeneral", "Restore Failed", ex);
MessageBox.Show($"복원 실패: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
#endregion
#region [Helper Methods]
/// <summary>
/// 값이 변경되었는지 확인하고, 변경되었다면 로그용 Dictionary에 추가합니다.
/// 저장 포맷: "[OldValue] -> [NewValue]"
/// </summary>
private void CheckChange(Dictionary<string, object> logs, string key, string oldValue, string newValue)
{
// Null 처리 및 문자열 비교
if ((oldValue ?? string.Empty) != (newValue ?? string.Empty))
{
logs.Add(key, $"[{oldValue}] -> [{newValue}]");
}
}
/// <summary>
/// Config 객체의 데이터를 UI 컨트롤에 바인딩합니다.
/// (Form_Load와 Restore 버튼에서 공통으로 사용)
/// </summary>
private void SetControlsFromConfig(SystemConfig config)
{
if (config == null) return;
txtEquipmentName.Text = config.EquipmentName;
// Enum -> ComboBox Index 변환
if (Enum.IsDefined(typeof(StageType), config.StageType))
{
cmbEquipmentType.SelectedIndex = (int)config.StageType;
}
// 1-based Index -> 0-based Index 변환 (범위 체크 포함)
int lineIdx = config.ProductLine - 1;
cmbLineIndex.SelectedIndex = (lineIdx >= 0 && lineIdx < cmbLineIndex.Items.Count) ? lineIdx : 0;
int pcIdx = config.PcNumber - 1;
cmbEquipmentIndex.SelectedIndex = (pcIdx >= 0 && pcIdx < cmbEquipmentIndex.Items.Count) ? pcIdx : 0;
txtSaveOrgImg.Text = config.SaveOrgImgPath;
txtSaveDetecImg.Text = config.SaveDetectImgPath;
}
#endregion
}
}
3. 적용 결과 (로그 파일 예시)
사용자가 장비 이름을 바꾸고 저장하면, 로그 파일(logs\...\SETTING_YYMMDD.log)에는 다음과 같이 명확한 증거가 남습니다.
Plaintext
[2026-01-08 14:30:00][SETTING][FormSystemGeneral] System Configuration Saved Equipmen
반응형