using Elwig.Helpers; using Elwig.Models.Entities; using System; using System.Collections.Generic; using System.ComponentModel; using Microsoft.EntityFrameworkCore; using System.Linq; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Elwig.Windows { public abstract class AdministrationWindow : ContextWindow { protected Control[] ExemptInputs { private get; set; } protected Control[] RequiredInputs { private get; set; } private bool _isEditing; private bool _isCreating; public bool IsEditing { get { return _isEditing; } set { _isEditing = value; LockContext = IsEditing || IsCreating; } } public bool IsCreating { get { return _isCreating; } set { _isCreating = value; LockContext = IsEditing || IsCreating; } } protected bool DoShowWarningWindows = true; protected bool IsClosing { get; private set; } private TextBox[] TextBoxInputs; private TextBox[] PlzInputs; private ComboBox[] ComboBoxInputs; private ComboBox[] PlzOrtInputs; private ListBox[] ListBoxInputs; private CheckBox[] CheckBoxInputs; private RadioButton[] RadioButtonInputs; private readonly Dictionary Valid; private readonly Dictionary OriginalValues; private readonly Dictionary DefaultValues; private readonly RoutedCommand AltInsert = new("AltInsert", typeof(AdministrationWindow), [new KeyGesture(Key.Insert, ModifierKeys.Alt)]); private readonly RoutedCommand AltDelete = new("AltDelete", typeof(AdministrationWindow), [new KeyGesture(Key.Delete, ModifierKeys.Alt)]); private readonly RoutedCommand CtrlZ = new("CtrlZ", typeof(AdministrationWindow), [new KeyGesture(Key.Z, ModifierKeys.Control)]); private readonly RoutedCommand CtrlS = new("CtrlS", typeof(AdministrationWindow), [new KeyGesture(Key.S, ModifierKeys.Control)]); private readonly RoutedCommand CtrlB = new("CtrlB", typeof(AdministrationWindow), [new KeyGesture(Key.B, ModifierKeys.Control)]); public AdministrationWindow() : base() { CommandBindings.Add(new CommandBinding(AltInsert, ShortcutNew)); CommandBindings.Add(new CommandBinding(AltDelete, ShortcutDelete)); CommandBindings.Add(new CommandBinding(CtrlZ, ShortcutReset)); CommandBindings.Add(new CommandBinding(CtrlS, ShortcutSave)); CommandBindings.Add(new CommandBinding(CtrlB, ShortcutEdit)); IsEditing = false; IsCreating = false; ExemptInputs = []; RequiredInputs = []; TextBoxInputs = []; PlzInputs = []; ComboBoxInputs = []; ListBoxInputs = []; PlzOrtInputs = []; CheckBoxInputs = []; RadioButtonInputs = []; Valid = []; OriginalValues = []; DefaultValues = []; Closing += OnClosing; Loaded -= base.OnLoaded; Loaded += OnLoaded; Loaded += base.OnLoaded; } abstract protected void ShortcutNew(); abstract protected void ShortcutDelete(); abstract protected void ShortcutReset(); abstract protected void ShortcutSave(); abstract protected void ShortcutEdit(); private void ShortcutNew(object sender, EventArgs evt) { ShortcutNew(); } private void ShortcutDelete(object sender, EventArgs evt) { ShortcutDelete(); } private void ShortcutReset(object sender, EventArgs evt) { ShortcutReset(); } private void ShortcutSave(object sender, EventArgs evt) { ShortcutSave(); } private void ShortcutEdit(object sender, EventArgs evt) { ShortcutEdit(); } private new void OnLoaded(object sender, RoutedEventArgs evt) { TextBoxInputs = ControlUtils.FindAllChildren(this, ExemptInputs).ToArray(); ComboBoxInputs = ControlUtils.FindAllChildren(this, ExemptInputs).ToArray(); CheckBoxInputs = ControlUtils.FindAllChildren(this, ExemptInputs).ToArray(); ListBoxInputs = ControlUtils.FindAllChildren(this, ExemptInputs).ToArray(); RadioButtonInputs = ControlUtils.FindAllChildren(this, ExemptInputs).ToArray(); PlzInputs = ControlUtils.FindAllChildren(this).Where(tb => "PLZ".Equals(tb.Tag)).ToArray(); PlzOrtInputs = PlzInputs.Select(tb => ControlUtils.FindNextSibling(tb) ?? throw new MissingMemberException()).ToArray(); foreach (var tb in TextBoxInputs) Valid[tb] = true; foreach (var cb in ComboBoxInputs) cb.SelectionChanged += ComboBox_SelectionChanged; foreach (var lb in ListBoxInputs) lb.SelectionChanged += ComboBox_SelectionChanged; } private void OnClosing(object? sender, CancelEventArgs evt) { if ((IsCreating || IsEditing) && HasChanged) { var r = MessageBox.Show("Soll das Fenster wirklich geschlossen werden?", "Schließen bestätigen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (r != MessageBoxResult.Yes) { evt.Cancel = true; return; } } IsClosing = true; } private ComboBox GetPlzOrtInput(TextBox input) { return PlzOrtInputs[Array.IndexOf(PlzInputs, input)]; } abstract protected void UpdateButtons(); protected override async Task OnRenewContext(AppDbContext ctx) { for (int i = 0; i < PlzInputs.Length; i++) await UpdatePlz(PlzInputs[i], PlzOrtInputs[i]); } protected void ValidateInput(Control input, bool valid) { Valid[input] = valid; } protected bool SenderIsRequired(object sender) { return (sender is Control c) && RequiredInputs.Contains(c); } protected void FinishInputFilling() { FillOriginalValues(); ValidateDefaultValues(); ValidateRequiredInputs(); } protected void ClearInputStates() { foreach (var tb in TextBoxInputs) ControlUtils.ClearInputState(tb); foreach (var cb in ComboBoxInputs) ControlUtils.ClearInputState(cb); foreach (var lb in ListBoxInputs) ControlUtils.ClearInputState(lb); foreach (var cb in CheckBoxInputs) ControlUtils.ClearInputState(cb); foreach (var rb in RadioButtonInputs) ControlUtils.ClearInputState(rb); } protected void ValidateRequiredInputs() { foreach (var input in RequiredInputs) { if (input is Control c && c.Visibility != Visibility.Visible) { ControlUtils.ClearInputState(input); Valid[input] = true; } else if (input is TextBox tb && tb.Text.Length == 0) { ControlUtils.SetInputInvalid(input); Valid[input] = false; } else if (input is ComboBox cb && cb.SelectedItem == null && cb.ItemsSource != null && cb.ItemsSource.Cast().Any()) { ControlUtils.SetInputInvalid(input); } else if (input is ListBox lb && lb.SelectedItem == null && lb.ItemsSource != null && lb.ItemsSource.Cast().Any()) { ControlUtils.SetInputInvalid(input); } else if (input is CheckBox ckb && ((ckb.IsThreeState && ckb.IsChecked == null) || (!ckb.IsThreeState && ckb.IsChecked != true))) { ControlUtils.SetInputInvalid(input); Valid[input] = false; } else if (input is RadioButton rb && rb.IsChecked != true) { ControlUtils.SetInputInvalid(input); Valid[input] = false; } } } protected void ValidateDefaultValues() { foreach (var input in DefaultValues.Keys) { if (InputIsNotDefault(input)) ControlUtils.SetInputNotDefault(input); } } protected void LockInputs() { foreach (var tb in TextBoxInputs) tb.IsReadOnly = true; foreach (var cb in ComboBoxInputs) cb.IsEnabled = false; foreach (var lb in ListBoxInputs) lb.IsEnabled = false; foreach (var cb in CheckBoxInputs) cb.IsEnabled = false; foreach (var rb in RadioButtonInputs) rb.IsEnabled = false; } protected void UnlockInputs() { foreach (var tb in TextBoxInputs) tb.IsReadOnly = false; foreach (var cb in ComboBoxInputs) cb.IsEnabled = true; foreach (var lb in ListBoxInputs) lb.IsEnabled = true; foreach (var cb in CheckBoxInputs) cb.IsEnabled = true; foreach (var rb in RadioButtonInputs) rb.IsEnabled = true; } protected void ClearOriginalValues() { OriginalValues.Clear(); } protected void ClearDefaultValues() { DefaultValues.Clear(); } protected void FillOriginalValues() { foreach (var tb in TextBoxInputs) OriginalValues[tb] = ControlUtils.GetInputHashCode(tb); foreach (var cb in ComboBoxInputs) OriginalValues[cb] = ControlUtils.GetInputHashCode(cb); foreach (var lb in ListBoxInputs) OriginalValues[lb] = ControlUtils.GetInputHashCode(lb); foreach (var cb in CheckBoxInputs) OriginalValues[cb] = ControlUtils.GetInputHashCode(cb); foreach (var rb in RadioButtonInputs) OriginalValues[rb] = ControlUtils.GetInputHashCode(rb); } protected void SetOriginalValue(Control input, object? value) { OriginalValues[input] = Utils.GetEntityIdentifier(value); if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else { ControlUtils.ClearInputState(input); } } protected void SetOriginalValue(Control input) { SetOriginalValue(input, ControlUtils.GetInputHashCode(input)); } protected void UnsetOriginalValue(Control input) { OriginalValues.Remove(input); ControlUtils.ClearInputState(input); } protected void SetDefaultValue(Control input, object? value) { DefaultValues[input] = Utils.GetEntityIdentifier(value); if (!InputHasChanged(input)) { if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } } protected void SetDefaultValue(Control input) { SetDefaultValue(input, ControlUtils.GetInputHashCode(input)); } protected void UnsetDefaultValue(Control input) { DefaultValues.Remove(input); if (!InputHasChanged(input)) { ControlUtils.ClearInputState(input); } } protected void ClearInputs(bool validate = true) { foreach (var tb in TextBoxInputs) { tb.Text = ""; var binding = tb.GetBindingExpression(TextBox.TextProperty); binding?.UpdateSource(); } foreach (var cb in ComboBoxInputs) { cb.SelectedItem = null; var binding = cb.GetBindingExpression(ComboBox.SelectedItemProperty); binding?.UpdateSource(); } foreach (var lb in ListBoxInputs) lb.SelectedItems.Clear(); foreach (var cb in CheckBoxInputs) { cb.IsChecked = cb.IsThreeState ? null : false; var binding = cb.GetBindingExpression(CheckBox.IsCheckedProperty); binding?.UpdateSource(); } foreach (var rb in RadioButtonInputs) { rb.IsChecked = rb.IsThreeState ? null : false; var binding = rb.GetBindingExpression(RadioButton.IsCheckedProperty); binding?.UpdateSource(); } if (validate) ValidateRequiredInputs(); } protected bool IsValid => Valid.All(kv => kv.Value); protected bool GetInputValid(Control input) { return Valid[input]; } protected bool InputHasChanged(Control input) { if (!OriginalValues.TryGetValue(input, out int? original)) { return false; } else { var current = ControlUtils.GetInputHashCode(input); return original != current; } } protected bool InputIsNotDefault(Control input) { if (!DefaultValues.TryGetValue(input, out int? defaultValue)) { return false; } else { var current = ControlUtils.GetInputHashCode(input); return defaultValue != current; } } protected bool HasChanged => IsEditing && ( !IsValid || TextBoxInputs.Any(InputHasChanged) || ComboBoxInputs.Any(InputHasChanged) || ListBoxInputs.Any(InputHasChanged) || CheckBoxInputs.Any(InputHasChanged) || RadioButtonInputs.Any(InputHasChanged) ) || IsCreating && ( TextBoxInputs.Any(i => InputIsNotDefault(i) || (!i.IsReadOnly && i.Text != "")) || ComboBoxInputs.Any(i => InputIsNotDefault(i) || (i.IsEnabled && i.SelectedItem != null)) || ListBoxInputs.Any(i => InputIsNotDefault(i) || i.SelectedItem != null) || CheckBoxInputs.Any(InputIsNotDefault) || RadioButtonInputs.Any(InputIsNotDefault) ); protected async Task UpdatePlz(TextBox plzInput, ComboBox ortInput) { var plzInputValid = Validator.CheckPlz(plzInput, RequiredInputs.Contains(plzInput)).IsValid; List list = []; if (plzInputValid && plzInput.Text.Length == 4) { var plz = int.Parse(plzInput.Text); using var ctx = new AppDbContext(); list = await ctx.PlzDestinations .Where(p => p.Plz == plz) .Include(p => p.Ort) .ToListAsync(); } ControlUtils.RenewItemsSource(ortInput, list); if (ortInput.SelectedItem == null && list.Count == 1) ortInput.SelectedItem = list[0]; UpdateComboBox(ortInput); } protected bool InputTextChanged(TextBox input) { return InputTextChanged(input, new ValidationResult(true, null)); } protected bool InputTextChanged(TextBox input, Func checker) { return InputTextChanged(input, checker(input, SenderIsRequired(input))); } protected bool InputTextChanged(TextBox input, ValidationResult res) { ValidateInput(input, res.IsValid); if (res.IsValid) { if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } else { ControlUtils.SetInputInvalid(input); } UpdateButtons(); return res.IsValid; } protected bool InputLostFocus(TextBox input, Func checker, string? msg = null) { return InputLostFocus(input, checker(input, SenderIsRequired(input)), msg); } protected bool InputLostFocus(TextBox input, ValidationResult res, string? msg = null) { if (DoShowWarningWindows && !res.IsValid && !IsClosing && (IsEditing || IsCreating)) MessageBox.Show(res.ErrorContent?.ToString(), msg ?? res.ErrorContent?.ToString(), MessageBoxButton.OK, MessageBoxImage.Warning); return res.IsValid; } protected void CheckBox_Changed(object sender, RoutedEventArgs evt) { var input = (CheckBox)sender; if (SenderIsRequired(input) && ((input.IsThreeState && input.IsChecked == null) || (!input.IsThreeState && input.IsChecked != true))) { ValidateInput(input, false); ControlUtils.SetInputInvalid(input); } else { ValidateInput(input, true); if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } UpdateButtons(); } protected void RadioButton_Changed(object sender, RoutedEventArgs evt) { var input = (RadioButton)sender; if (SenderIsRequired(input) && input.IsChecked != true) { ValidateInput(input, false); ControlUtils.SetInputInvalid(input); } else { ValidateInput(input, true); if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } UpdateButtons(); } protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) { var input = (Control)sender; var tb = input as TextBox; if (SenderIsRequired(input) && tb?.Text.Length == 0) { ValidateInput(input, false); ControlUtils.SetInputInvalid(input); } else { ValidateInput(input, true); if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } UpdateButtons(); } private void UpdateComboBox(Control input) { bool valid = false; if (input is ComboBox cb) { valid = cb.ItemsSource == null || cb.SelectedItem != null || !RequiredInputs.Contains(input) || !cb.ItemsSource.Cast().Any(); } else if (input is ListBox lb) { valid = lb.ItemsSource == null || lb.SelectedItem != null || !RequiredInputs.Contains(input) || !lb.ItemsSource.Cast().Any(); } if (valid) { ValidateInput(input, true); if (InputHasChanged(input)) { ControlUtils.SetInputChanged(input); } else if (InputIsNotDefault(input)) { ControlUtils.SetInputNotDefault(input); } else { ControlUtils.ClearInputState(input); } } else { ValidateInput(input, false); ControlUtils.SetInputInvalid(input); } UpdateButtons(); } protected void ComboBox_SelectionChanged(object sender, RoutedEventArgs? evt) { UpdateComboBox((Control)sender); } protected void IntegerInput_TextChanged(object sender, TextChangedEventArgs? evt) { // FIXME InputTextChanged((TextBox)sender, Validator.CheckInteger); } protected void DecimalInput_TextChanged(object sender, TextChangedEventArgs? evt) { // FIXME InputTextChanged((TextBox)sender, Validator.CheckDecimal); } protected void PartialDateInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckPartialDate); } protected void PartialDateInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckPartialDate); } protected void DateInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckDate); } protected void DateInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckDate); } protected void TimeInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckTime); } protected void TimeInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckTime); } protected async void PlzInput_TextChanged(object sender, TextChangedEventArgs? evt) { var plz = (TextBox)sender; InputTextChanged(plz, Validator.CheckPlz); if ("PLZ".Equals(plz.Tag)) await UpdatePlz(plz, GetPlzOrtInput(plz)); } protected async void PlzInput_LostFocus(object sender, RoutedEventArgs? evt) { var plz = (TextBox)sender; InputLostFocus(plz, Validator.CheckPlz); if ("PLZ".Equals(plz.Tag)) await UpdatePlz(plz, GetPlzOrtInput(plz)); } protected void EmailAddressInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckEmailAddress); } protected void EmailAddressInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckEmailAddress); } protected void PhoneNrInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckPhoneNumber); } protected void PhoneNrInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckPhoneNumber); } protected void IbanInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckIban); } protected void IbanInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckIban); } protected void BicInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckBic); } protected void BicInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckBic); } protected void UstIdNrInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckUstIdNr); } protected void UstIdNrInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckUstIdNr); } protected void LfbisNrInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckLfbisNr); } protected void LfbisNrInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckLfbisNr); } protected void UpperCaseInput_TextChanged(object sender, TextChangedEventArgs? evt) { InputTextChanged((TextBox)sender, Validator.CheckUpperCase); } protected void UpperCaseInput_LostFocus(object sender, RoutedEventArgs? evt) { InputLostFocus((TextBox)sender, Validator.CheckUpperCase); } protected void AreaComTypeDetailsButton_Click(object sender, RoutedEventArgs? evt) { App.FocusBaseDataAreaComType(); } } }