From 7468af3970ff6e27de407606785b6b6bb569be31 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Thu, 25 May 2023 18:03:38 +0200 Subject: [PATCH] Add RenewContext --- Elwig/Helpers/AppDbContext.cs | 10 ++++ Elwig/Helpers/Utils.cs | 35 ++++++++++++ Elwig/Windows/AdministrationWindow.cs | 66 +++++++++++++++++++---- Elwig/Windows/AreaComAdminWindow.xaml.cs | 4 ++ Elwig/Windows/ClientParamWindow.xaml.cs | 6 +++ Elwig/Windows/ContextWindow.cs | 26 ++++++++- Elwig/Windows/DeliveryAdminWindow.xaml.cs | 43 +++++++++------ Elwig/Windows/MemberAdminWindow.xaml | 8 +-- Elwig/Windows/MemberAdminWindow.xaml.cs | 22 ++++---- 9 files changed, 177 insertions(+), 43 deletions(-) diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index f41a440..1b8a77e 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -40,6 +40,9 @@ namespace Elwig.Helpers { public DbSet DeliveryPartModifiers { get; private set; } private readonly StreamWriter? LogFile = null; + public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile); + public DateTime SavedLastWriteTime { get; private set; } + public bool HasBackendChanged => SavedLastWriteTime != LastWriteTime; public AppDbContext() { if (App.Config.DatabaseLog != null) { @@ -52,6 +55,9 @@ namespace Elwig.Helpers { MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error); } } + SavedLastWriteTime = LastWriteTime; + SavedChanges += OnSavedChanges; + } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -67,6 +73,10 @@ namespace Elwig.Helpers { GC.SuppressFinalize(this); } + private void OnSavedChanges(object? sender, SavedChangesEventArgs evt) { + SavedLastWriteTime = LastWriteTime; + } + protected void Log(string msg) { LogFile?.WriteLine(msg); } diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index 18d1d42..0d84116 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -10,6 +10,7 @@ using System.Windows.Controls.Primitives; using System.Text.RegularExpressions; using System.IO.Ports; using System.Net.Sockets; +using System.Collections; namespace Elwig.Helpers { public static partial class Utils { @@ -129,6 +130,40 @@ namespace Elwig.Helpers { return null; } + public static void RenewItemsSource(Selector selector, IEnumerable? source, Func getId) { + var selectedId = getId(selector.SelectedItem); + selector.ItemsSource = source; + if (selectedId != null && source != null) + selector.SelectedItem = source.Cast().FirstOrDefault(i => selectedId.Equals(getId(i))); + } + + public static void RenewItemsSource(Xceed.Wpf.Toolkit.Primitives.Selector selector, IEnumerable? source, Func getId) { + var selectedIds = selector.SelectedItems.Cast().Select(i => getId(i)).ToList(); + selector.ItemsSource = source; + if (source != null) { + foreach (var i in source.Cast().Where(i => selectedIds.Contains(getId(i)))) + selector.SelectedItems.Add(i); + } + } + + public static void RenewItemsSource(DataGrid dataGrid, IEnumerable? source, Func getId, bool keepSort = true) { + var column = dataGrid.CurrentCell.Column; + var sortColumns = dataGrid.Columns.Select(c => c.SortDirection).ToList(); + var sort = dataGrid.Items.SortDescriptions.ToList(); + var selectedId = getId(dataGrid.SelectedItem); + dataGrid.ItemsSource = source; + if (keepSort) { + for (int i = 0; i < dataGrid.Columns.Count; i++) + dataGrid.Columns[i].SortDirection = sortColumns[i]; + foreach (var s in sort) + dataGrid.Items.SortDescriptions.Add(s); + } + if (selectedId != null && source != null) + dataGrid.SelectedItem = source.Cast().FirstOrDefault(i => selectedId.Equals(getId(i))); + if (dataGrid.SelectedItem != null) + dataGrid.CurrentCell = new(dataGrid.SelectedItem, column); + } + public static int Modulo(string a, int b) { if (a.Length == 0 || !a.All(char.IsAsciiDigit)) { throw new ArgumentException("First argument has to be a decimal string"); diff --git a/Elwig/Windows/AdministrationWindow.cs b/Elwig/Windows/AdministrationWindow.cs index 700ca64..15be937 100644 --- a/Elwig/Windows/AdministrationWindow.cs +++ b/Elwig/Windows/AdministrationWindow.cs @@ -1,27 +1,56 @@ using Elwig.Helpers; +using Elwig.Models; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using Xceed.Wpf.Toolkit; 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; + protected bool IsEditing { + get { return _IsEditing; } + set { + _IsEditing = value; + LockContext = IsEditing || IsCreating; + } + } + protected bool IsCreating { + get { return _IsCreating; } + set { + _IsCreating = value; + LockContext = IsEditing || IsCreating; + } + } + private TextBox[] TextBoxInputs; + private TextBox[] PlzInputs; private ComboBox[] ComboBoxInputs; + private ComboBox[] PlzOrtInputs; + private CheckComboBox[] CheckComboBoxInputs; private CheckBox[] CheckBoxInputs; private RadioButton[] RadioButtonInputs; private readonly Dictionary Valid; private readonly Dictionary OriginalValues; public AdministrationWindow() : base() { + IsEditing = false; + IsCreating = false; ExemptInputs = Array.Empty(); RequiredInputs = Array.Empty(); TextBoxInputs = Array.Empty(); + PlzInputs = Array.Empty(); ComboBoxInputs = Array.Empty(); + CheckComboBoxInputs = Array.Empty(); + PlzOrtInputs = Array.Empty(); CheckBoxInputs = Array.Empty(); RadioButtonInputs = Array.Empty(); Valid = new(); @@ -33,14 +62,30 @@ namespace Elwig.Windows { TextBoxInputs = Utils.FindAllChildren(this, ExemptInputs).ToArray(); ComboBoxInputs = Utils.FindAllChildren(this, ExemptInputs).ToArray(); CheckBoxInputs = Utils.FindAllChildren(this, ExemptInputs).ToArray(); + CheckComboBoxInputs = Utils.FindAllChildren(this, ExemptInputs).ToArray(); RadioButtonInputs = Utils.FindAllChildren(this, ExemptInputs).ToArray(); + PlzInputs = TextBoxInputs.Where(tb => "PLZ".Equals(tb.Tag)).ToArray(); + PlzOrtInputs = PlzInputs.Select(tb => Utils.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 cb in CheckComboBoxInputs) + cb.ItemSelectionChanged += ComboBox_SelectionChanged; ValidateRequiredInputs(); } + private ComboBox GetPlzOrtInput(TextBox input) { + return PlzOrtInputs[Array.IndexOf(PlzInputs, input)]; + } + abstract protected void UpdateButtons(); + protected override async Task RenewContext() { + for (int i = 0; i < PlzInputs.Length; i++) + UpdatePlz(PlzInputs[i], PlzOrtInputs[i]); + } + protected void ValidateInput(Control input, bool valid) { Valid[input] = valid; } @@ -122,6 +167,10 @@ namespace Elwig.Windows { protected bool IsValid => Valid.All(kv => kv.Value); + protected bool GetInputValid(Control input) { + return Valid[input]; + } + protected bool InputHasChanged(Control input) { if (!OriginalValues.ContainsKey(input)) { return false; @@ -145,14 +194,13 @@ namespace Elwig.Windows { CheckBoxInputs.Any(InputHasChanged) || RadioButtonInputs.Any(InputHasChanged); - protected void UpdatePlz(TextBox plzInput, bool plzInputValid, ComboBox ortInput) { + protected void UpdatePlz(TextBox plzInput, ComboBox ortInput) { + var plzInputValid = GetInputValid(plzInput); var item = ortInput.SelectedItem; var list = plzInputValid && plzInput.Text.Length == 4 ? Context.Postleitzahlen.Find(int.Parse(plzInput.Text))?.Orte.ToList() : null; - ortInput.ItemsSource = list; - ortInput.SelectedItem = (list != null && item != null && list.Contains(item)) ? item : null; + Utils.RenewItemsSource(ortInput, list, i => (i as AT_PlzDest)?.Id); if (list != null && ortInput.SelectedItem == null && list.Count == 1) ortInput.SelectedItem = list[0]; - ComboBox_SelectionChanged(ortInput, new()); } protected bool InputTextChanged(TextBox input, Func checker) { @@ -188,7 +236,7 @@ namespace Elwig.Windows { protected bool InputLostFocus(TextBox input, ValidationResult res, string? msg = null) { if (!res.IsValid) - MessageBox.Show(res.ErrorContent.ToString(), msg ?? res.ErrorContent.ToString(), MessageBoxButton.OK, MessageBoxImage.Warning); + System.Windows.MessageBox.Show(res.ErrorContent.ToString(), msg ?? res.ErrorContent.ToString(), MessageBoxButton.OK, MessageBoxImage.Warning); return res.IsValid; } @@ -270,14 +318,14 @@ namespace Elwig.Windows { protected void PlzInput_TextChanged(object sender, RoutedEventArgs evt) { var plz = (TextBox)sender; - var valid = InputTextChanged(plz, Validator.CheckPlz); - UpdatePlz(plz, valid, Utils.FindNextSibling(plz)); + InputTextChanged(plz, Validator.CheckPlz); + UpdatePlz(plz, GetPlzOrtInput(plz)); } protected void PlzInput_LostFocus(object sender, RoutedEventArgs evt) { var plz = (TextBox)sender; - var valid = InputLostFocus(plz, Validator.CheckPlz); - UpdatePlz(plz, valid, Utils.FindNextSibling(plz)); + InputLostFocus(plz, Validator.CheckPlz); + UpdatePlz(plz, GetPlzOrtInput(plz)); } protected void EmailInput_TextChanged(object sender, RoutedEventArgs evt) { diff --git a/Elwig/Windows/AreaComAdminWindow.xaml.cs b/Elwig/Windows/AreaComAdminWindow.xaml.cs index 2730698..f502463 100644 --- a/Elwig/Windows/AreaComAdminWindow.xaml.cs +++ b/Elwig/Windows/AreaComAdminWindow.xaml.cs @@ -39,6 +39,10 @@ namespace Elwig.Windows { CultInput.ItemsSource = Context.WineCultivations.OrderBy(c => c.Name).ToList(); } + protected override async Task RenewContext() { + await base.RenewContext(); + } + private async Task RefreshContractList() { /* await Context.Contracts.LoadAsync(); diff --git a/Elwig/Windows/ClientParamWindow.xaml.cs b/Elwig/Windows/ClientParamWindow.xaml.cs index d3c22b4..69c214a 100644 --- a/Elwig/Windows/ClientParamWindow.xaml.cs +++ b/Elwig/Windows/ClientParamWindow.xaml.cs @@ -1,4 +1,6 @@ +using System.Threading.Tasks; + namespace Elwig.Windows { public partial class ClientParamWindow : AdministrationWindow { public ClientParamWindow() { @@ -8,5 +10,9 @@ namespace Elwig.Windows { protected override void UpdateButtons() { } + + protected override async Task RenewContext() { + + } } } diff --git a/Elwig/Windows/ContextWindow.cs b/Elwig/Windows/ContextWindow.cs index 4e7df6e..8d5df37 100644 --- a/Elwig/Windows/ContextWindow.cs +++ b/Elwig/Windows/ContextWindow.cs @@ -1,19 +1,43 @@ using Elwig.Helpers; using System; +using System.Threading.Tasks; using System.Windows; +using System.Windows.Threading; namespace Elwig.Windows { public abstract class ContextWindow : Window { - protected readonly AppDbContext Context; + protected AppDbContext Context { get; private set; } + protected bool LockContext { get; set; } = false; + + private readonly DispatcherTimer ContextRenewTimer; + private static readonly int ContextRenewSec = 10; public ContextWindow() : base() { + ContextRenewTimer = new DispatcherTimer(); + ContextRenewTimer.Tick += new EventHandler(OnRenewContext); + ContextRenewTimer.Interval = new TimeSpan(0, 0, ContextRenewSec); + ContextRenewTimer.Start(); Context = new(); + Loaded += OnLoaded; + } + + private void OnRenewContext(object? sender, EventArgs evt) { + if (LockContext || !Context.HasBackendChanged) return; + Context.Dispose(); + Context = new(); + RenewContext(); + } + + private void OnLoaded(object sender, RoutedEventArgs evt) { + RenewContext(); } protected override void OnClosed(EventArgs evt) { base.OnClosed(evt); Context.Dispose(); } + + abstract protected Task RenewContext(); } } diff --git a/Elwig/Windows/DeliveryAdminWindow.xaml.cs b/Elwig/Windows/DeliveryAdminWindow.xaml.cs index e6422fb..58ec6b0 100644 --- a/Elwig/Windows/DeliveryAdminWindow.xaml.cs +++ b/Elwig/Windows/DeliveryAdminWindow.xaml.cs @@ -1,8 +1,10 @@ using Elwig.Helpers; using Elwig.Models; +using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; @@ -25,7 +27,6 @@ namespace Elwig.Windows { WineOriginInput, WineKgInput }; ExemptInputs = new Control[] { - MgNrInput, MemberInput, LsNrInput, DateInput, TimeInput, BranchInput, MemberAddressField }; @@ -37,15 +38,7 @@ namespace Elwig.Windows { } private void Window_Loaded(object sender, RoutedEventArgs evt) { - MemberInput.ItemsSource = Context.Members.OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToList(); - BranchInput.ItemsSource = Context.Branches.OrderBy(b => b.Name).ToList(); - BranchInput.SelectedItem = BranchInput.ItemsSource.Cast().First(b => b.ZwstId == App.ZwstId); - WineVarietyInput.ItemsSource = Context.WineVarieties.OrderBy(v => v.Name).ToList(); - AttributesInput.ItemsSource = Context.WineAttributes.OrderBy(a => a.Name).ToList(); - WineQualityLevelInput.ItemsSource = Context.WineQualityLevels.ToList(); - ModifiersInput.ItemsSource = Context.Modifiers.Where(m => m.Season.Year == 2022).OrderBy(m => m.Name).ToList(); - WineOriginInput.ItemsSource = Context.WineOrigins.ToList().OrderByDescending(o => o.SortKey).ThenBy(o => o.HkId); - WineKgInput.ItemsSource = Context.WbKgs.Select(k => k.AtKg).OrderBy(k => k.Name).ToList(); + } private void OnSecondPassed(object? sender, EventArgs evt) { @@ -58,6 +51,21 @@ namespace Elwig.Windows { } + protected override async Task RenewContext() { + await base.RenewContext(); + Utils.RenewItemsSource(MemberInput, await Context.Members.OrderBy(m => m.FamilyName).ThenBy(m => m.GivenName).ToListAsync(), i => (i as Member)?.MgNr); + Utils.RenewItemsSource(BranchInput, await Context.Branches.OrderBy(b => b.Name).ToListAsync(), i => (i as Branch)?.ZwstId); + BranchInput.SelectedItem = BranchInput.ItemsSource.Cast().First(b => b.ZwstId == App.ZwstId); + Utils.RenewItemsSource(WineVarietyInput, await Context.WineVarieties.OrderBy(v => v.Name).ToListAsync(), i => (i as WineVar)?.SortId); + Utils.RenewItemsSource(AttributesInput, await Context.WineAttributes.OrderBy(a => a.Name).ToListAsync(), i => (i as WineAttr)?.AttrId); + Utils.RenewItemsSource(WineQualityLevelInput, await Context.WineQualityLevels.ToListAsync(), i => (i as WineQualLevel)?.QualId); + Utils.RenewItemsSource(ModifiersInput, await Context.Modifiers.Where(m => m.Season.Year == 2022).OrderBy(m => m.Name).ToListAsync(), i => (i as Modifier)?.ModId); + Utils.RenewItemsSource(WineOriginInput, (await Context.WineOrigins.ToListAsync()).OrderByDescending(o => o.SortKey).ThenBy(o => o.HkId), i => (i as WineOrigin)?.HkId); + Utils.RenewItemsSource(WineKgInput, await Context.WbKgs.Select(k => k.AtKg).OrderBy(k => k.Name).ToListAsync(), i => (i as AT_Kg)?.KgNr); + UpdateWineQualityLevels(); + UpdateRdInput(); + } + private void MgNrInput_TextChanged(object sender, TextChangedEventArgs evt) { var valid = InputTextChanged((TextBox)sender, Validator.CheckMgNr); MemberInput.SelectedItem = valid ? Context.Members.Find(int.Parse(MgNrInput.Text)) : null; @@ -177,17 +185,20 @@ namespace Elwig.Windows { UpdateWineOrigin(); } - private void WineKgInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) { - UpdateWineOrigin(); - var kg = (AT_Kg)WineKgInput.SelectedItem; - if (kg != null) { + private void UpdateRdInput() { + if (WineKgInput.SelectedItem is AT_Kg kg) { var list = Context.WbRde.Where(r => r.KgNr == kg.KgNr).OrderBy(r => r.Name).Cast().ToList(); list.Insert(0, new NullItem()); - WineRdInput.ItemsSource = list; - WineRdInput.SelectedIndex = 0; + Utils.RenewItemsSource(WineRdInput, list, i => ((i as WbRd)?.KgNr, (i as WbRd)?.RdNr)); + if (WineRdInput.SelectedItem == null) WineRdInput.SelectedIndex = 0; } else { WineRdInput.ItemsSource = null; } } + + private void WineKgInput_SelectionChanged(object sender, SelectionChangedEventArgs evt) { + UpdateWineOrigin(); + UpdateRdInput(); + } } } diff --git a/Elwig/Windows/MemberAdminWindow.xaml b/Elwig/Windows/MemberAdminWindow.xaml index 8b57c04..562faa4 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml +++ b/Elwig/Windows/MemberAdminWindow.xaml @@ -152,9 +152,8 @@