using System.Globalization; using System.IO; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Threading; using Housing.Login; using Housing.Models; using Housing.Services; namespace Housing; public partial class MainWindow : Window { private const string BarcodeRequiredMessage = "PCB_Barcode를 입력하고 Enter를 눌러주세요."; private const string BoardReadyMessage = "Enter를 누르면 보드 연결 테스트를 시작합니다."; private const string DefaultOutputValue = "0.00"; private StartupLoginWindowResult _loginResult = new(); private InspectionJudgementSettings _inspectionSettings; private bool _isStartupLoginShown; private bool _isBoardTestRunning; public MainWindow() { _inspectionSettings = InspectionSettingsStore.Load(GetInspectionSettingsIniPath()); InitializeComponent(); ApplyInspectionSettingsToTextBoxes(); ApplyLoginResult(); ResetScreen(); } protected override void OnContentRendered(EventArgs e) { base.OnContentRendered(e); if (_isStartupLoginShown) { return; } _isStartupLoginShown = true; if (!ShowLoginPopup()) { Close(); } } private void BarcodeTextBox_TextChanged(object sender, TextChangedEventArgs e) { if (string.IsNullOrWhiteSpace(BarcodeTextBox.Text)) { IcSnOutputTextBox.Clear(); ResultOutputTextBox.Text = BarcodeRequiredMessage; return; } ResultOutputTextBox.Text = BoardReadyMessage; } private async void BarcodeTextBox_KeyDown(object sender, KeyEventArgs e) { if (e.Key != Key.Enter && e.Key != Key.Return) { return; } e.Handled = true; await RunBoardSequenceAsync(); } public async Task OnEquipmentStartButtonPressedAsync() { await RunBoardSequenceAsync(); } private async Task RunBoardSequenceAsync() { if (_isBoardTestRunning) { return; } if (string.IsNullOrWhiteSpace(BarcodeTextBox.Text)) { ResultOutputTextBox.Text = BarcodeRequiredMessage; FocusBarcodeInput(); return; } if (!TryApplyInspectionSettingsFromTextBoxes()) { return; } try { _isBoardTestRunning = true; SetInputEnabled(false); ResultOutputTextBox.Text = "보드 테스트 시작\r\n" + "1. 시료 연결 확인\r\n" + "2. IC_SN 읽기\r\n" + "3. CAL DEFAULT 조건 설정\r\n" + "4. 계측 장비에서 V/A 읽기"; var boardSettings = BoardHardwareSettings.Load(GetHardwareIniPath()); var equipmentSettings = EquipmentMeasurementSettings.Load(GetHardwareIniPath()); var service = new BoardTestService(boardSettings, equipmentSettings); var measurement = await service.RunAsync(); IcSnOutputTextBox.Text = measurement.IcSn; VOutputTextBox.Text = FormatDecimal(measurement.Voltage); AOutputTextBox.Text = FormatDecimal(measurement.Current); var resultCode = _inspectionSettings.IsPass(measurement.Voltage, measurement.Current) ? "OK" : "NG"; ResultOutputTextBox.Text = $"{resultCode}\r\n{measurement.RawLog}"; await SaveCurrentResultAsync(resultCode); } catch (Exception ex) { ResultOutputTextBox.Text = $"보드 테스트 실패: {ex.Message}"; } finally { _isBoardTestRunning = false; SetInputEnabled(true); FocusBarcodeInput(); } } private void LogoutButton_Click(object sender, RoutedEventArgs e) { ResetScreen(); _loginResult = new StartupLoginWindowResult(); OperatorTextBlock.Text = "-"; LogoutButton.Visibility = Visibility.Collapsed; if (!ShowLoginPopup()) { Close(); } } private bool ShowLoginPopup() { var loginWindow = new StartupLoginWindow { Owner = this }; if (loginWindow.ShowDialog() != true) { return false; } _loginResult = loginWindow.Result; ApplyLoginResult(); ResetScreen(); return true; } private void ApplyLoginResult() { OperatorTextBlock.Text = string.IsNullOrWhiteSpace(_loginResult.Operator) ? "-" : _loginResult.Operator; LogoutButton.Visibility = string.IsNullOrWhiteSpace(_loginResult.Operator) ? Visibility.Collapsed : Visibility.Visible; } private void ResetScreen() { BarcodeTextBox.Clear(); IcSnOutputTextBox.Clear(); VOutputTextBox.Text = DefaultOutputValue; AOutputTextBox.Text = DefaultOutputValue; ResultOutputTextBox.Text = BarcodeRequiredMessage; FocusBarcodeInput(); } private void SetInputEnabled(bool isEnabled) { BarcodeTextBox.IsEnabled = isEnabled; VMinTextBox.IsEnabled = isEnabled; VMaxTextBox.IsEnabled = isEnabled; AMinTextBox.IsEnabled = isEnabled; AMaxTextBox.IsEnabled = isEnabled; LogoutButton.IsEnabled = isEnabled; } private void FocusBarcodeInput() { Dispatcher.BeginInvoke(new Action(() => { BarcodeTextBox.Focus(); Keyboard.Focus(BarcodeTextBox); BarcodeTextBox.SelectAll(); }), DispatcherPriority.Input); } private async Task SaveCurrentResultAsync(string resultCode) { try { var repository = new HousingAssemblyRepository(DatabaseSettings.Load(GetDatabaseIniPath())); var record = CreateCurrentRecord(resultCode); await repository.InsertAsync(record); ResultOutputTextBox.Text += "\r\nDB 저장 완료"; } catch (Exception ex) { ResultOutputTextBox.Text += $"\r\nDB 저장 실패: {ex.Message}"; } } private HousingAssemblyRecord CreateCurrentRecord(string resultCode) { var barcode = BarcodeTextBox.Text.Trim(); var icSn = IcSnOutputTextBox.Text.Trim(); if (string.IsNullOrWhiteSpace(icSn)) { icSn = barcode; IcSnOutputTextBox.Text = icSn; } return new HousingAssemblyRecord { IcSn = icSn, PcbBarcode = barcode, Maker = _loginResult.Maker, Model = _loginResult.Model, Variant1 = _loginResult.Variant1, Variant2 = _loginResult.Variant2, Operator = _loginResult.Operator, Password = _loginResult.Password, ProductionDate = DateTime.Now, Line = _loginResult.LineNo, LotNo = _loginResult.LotNo, JigNo = _loginResult.JigNo, PtVol1 = ParseDecimal(VOutputTextBox.Text), PtCurrent1 = ParseDecimal(AOutputTextBox.Text), Result = resultCode }; } private void ApplyInspectionSettingsToTextBoxes() { VMinTextBox.Text = FormatDecimal(_inspectionSettings.VMin); VMaxTextBox.Text = FormatDecimal(_inspectionSettings.VMax); AMinTextBox.Text = FormatDecimal(_inspectionSettings.AMin); AMaxTextBox.Text = FormatDecimal(_inspectionSettings.AMax); } private bool TryApplyInspectionSettingsFromTextBoxes() { if (!TryReadRangeValue(VMinTextBox, "V 최소값", out var vMin) || !TryReadRangeValue(VMaxTextBox, "V 최대값", out var vMax) || !TryReadRangeValue(AMinTextBox, "A 최소값", out var aMin) || !TryReadRangeValue(AMaxTextBox, "A 최대값", out var aMax)) { return false; } if (vMin > vMax) { MessageBox.Show(this, "V 최소값은 최대값보다 클 수 없습니다.", "판정 범위", MessageBoxButton.OK, MessageBoxImage.Warning); VMinTextBox.Focus(); return false; } if (aMin > aMax) { MessageBox.Show(this, "A 최소값은 최대값보다 클 수 없습니다.", "판정 범위", MessageBoxButton.OK, MessageBoxImage.Warning); AMinTextBox.Focus(); return false; } _inspectionSettings = new InspectionJudgementSettings { VMin = vMin, VMax = vMax, AMin = aMin, AMax = aMax }; InspectionSettingsStore.Save(GetInspectionSettingsIniPath(), _inspectionSettings); return true; } private bool TryReadRangeValue(TextBox textBox, string fieldName, out decimal value) { if (decimal.TryParse(textBox.Text, NumberStyles.Number, CultureInfo.InvariantCulture, out value)) { return true; } if (decimal.TryParse(textBox.Text, out value)) { return true; } MessageBox.Show(this, $"{fieldName}은 숫자로 입력하세요.", "판정 범위", MessageBoxButton.OK, MessageBoxImage.Warning); textBox.Focus(); return false; } private static decimal ParseDecimal(string value) { return decimal.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var result) ? result : 0m; } private static string FormatDecimal(decimal value) { return value.ToString("0.00", CultureInfo.InvariantCulture); } private static string GetDatabaseIniPath() { return Path.Combine(AppContext.BaseDirectory, "Database.ini"); } private static string GetHardwareIniPath() { return Path.Combine(AppContext.BaseDirectory, "Hardware.ini"); } private static string GetInspectionSettingsIniPath() { return Path.Combine(AppContext.BaseDirectory, "InspectionSettings.ini"); } }