diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index b64aeaf..b007d5d 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -170,16 +170,25 @@ namespace Elwig.Helpers { SavedChanges += OnSavedChanges; } + private static void CreateFunctions(SqliteConnection cnx) { + cnx.CreateFunction("REGEXP", (pattern, value) => { + return value == null ? null : Regex.IsMatch(value, pattern); + }, true); + cnx.CreateFunction("NORMALIZE_ADDRESS", (address, locality) => { + return string.IsNullOrWhiteSpace(address) ? null : Normalizer.NormalizeAddress(address, locality); + }, true); + } + public static SqliteConnection Connect(string? connectionString = null) { var cnx = new SqliteConnection(connectionString ?? ConnectionString); - cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); + CreateFunctions(cnx); cnx.Open(); return cnx; } public static async Task ConnectAsync(string? connectionString = null) { var cnx = new SqliteConnection(connectionString ?? ConnectionString); - cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); + CreateFunctions(cnx); await cnx.OpenAsync(); return cnx; } diff --git a/Elwig/Helpers/Normalizer.cs b/Elwig/Helpers/Normalizer.cs new file mode 100644 index 0000000..d37c9ed --- /dev/null +++ b/Elwig/Helpers/Normalizer.cs @@ -0,0 +1,303 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Elwig.Helpers { + public static partial class Normalizer { + + [GeneratedRegex(@"(?=(?:(?:bundes)?straße|gasse|weg|platz|allee|ring|zeile?|steig|hof|markt|kai|ufer|brücke|tor|stiege|promenade|gang|siedlung)\b)|[\s-]+", RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase)] + private static partial Regex GeneratedStreetNameWordsRegex(); + private static readonly Regex StreetNameWordsRegex = GeneratedStreetNameWordsRegex(); + + private static readonly HashSet StreetTypes = [ + "Straße", "Gasse", "Weg", "Platz", "Allee", "Ring", "Zeile", "Zeil", "Steig", "Hof", + "Markt", "Kai", "Ufer", "Brücke", "Tor", "Stiege", "Promenade", "Gang", "Siedlung"]; + + private static readonly HashSet StreetArticles = ["der", "die", "das", "des", "dem", "den"]; + + private static readonly HashSet StreetPrepositions = [ + "an", "am", "auf", "aus", "bei", "beim", "hinter", "hinterm", "in", "im", "ins", "nach", + "neben", "über", "unter", "von", "vom", "van", "vor", "zu", "zum", "zur", "zwischen", + "durch", "mit"]; + + private static readonly HashSet StreetNumbers = [ "Zwei", "Drei", "Vier", "Fünf", "Sechs", "Sieben" ]; + + private static readonly HashSet StreetAdjectives = [ + "alter", "alte", "alten", "altem", "altes", + "äußere", "äußere", "äußeren", "äußerem", "äußeres", + // "breiter", "breite", "breiten", "breitem", "breites", + "dritter", "dritte", "dritten", "drittem", "drittes", + "erster", "erste", "ersten", "erstem", "erstes", + "großer", "große", "großen", "großem", "großes", + "hoher", "hohe", "hohen", "hohem", "hohes", + "innerer", "innere", "inneren", "innerem", "inneres", + "kleiner", "kleine", "kleinen", "kleinem", "kleines", + "kurzer", "kurze", "kurzen", "kurzem", "kurzes", + "langer", "lange", "langen", "langem", "langes", + "linker", "linke", "linken", "linkem", "linkes", + "mittlerer", "mittlere", "mittleren", "mittlerem", "mittleres", + "neuer", "neue", "neuen", "neuem", "neues", + "oberer", "obere", "oberen", "oberem", "oberes", + "rechter", "rechte", "rechten", "rechtem", "rechtes", + "schmaler", "schmale", "schmalen", "schmalem", "schmales", + "schöner", "schöne", "schönen", "schönem", "schönes", + "unterer", "untere", "unteren", "unterem", "unteres", + "hinterer", "hintere", "hinteren", "hinterem", "hinteres", + "vorderer", "vordere", "vorderen", "vorderem", "vorderes", + "runder", "runde", "runden", "rundem", "rundes", + "kalter", "kalte", "kalten", "kaltem", "kaltes"]; + + private static readonly HashSet StreetLastNames = [ + "Habsburger", "Lothringer", "Leitzenberger", "Luyderer", "Preiner", "Radinger", "Seefelder", "Staribacher", "Gschmeidler", + "Hannover", "Münster", "Salzgitter", "Speyer", "Trier"]; + + private static readonly HashSet StreetPersonTitles = [ + "St.", "Familie", + "Dr.", "DDr.", "Hofrat", "Baurat", "Kommerzialrat", "Dir.", "Ing.", "Prof.", "Mag.", "DI", "OSR", "Präsident", + "Landeshauptmann", "LH", "Bürgermeister", "Bgm.", "Minister", + "Kaiser", "Kaiserin", "König", "Königin", "Erzherzog", "Erzherzogin", "Herzog", "Fürst", "Graf", "Gräfin", "Markgraf", "Kronprinz", "Prinz", "Ritter", "Freiherr", "Lord", + "Abt", "Bischof", "Propst", "Dechant", "Kardinal", "Monsignore", "Pfarrer", "Pater", "Prälat", + "Oberst"]; + + private static readonly HashSet StreetGivenNames = [ + "Adalbert", "Adam", "Adele", "Adelheid", "Adolf", "Adolph", "Adrian", "Albert", "Albrecht", "Alexander", "Alfons", "Alfred", "Alois", "Aloisia", + "Andreas", "Andrea", "Andre", "Anna", "Anton", "Arnold", "Arthur", "August", "Augustin", "Augustine", "Agnes", "Angelika", "Ada", "Adelheid", "Anni", "Anita", + "Barbara", "Beate", "Beatrix", "Benedikt", "Bernhard", "Berta", "Bertha", "Berthold", "Brüder", "Bruno", "Brigitte", + "Camillo", "Carl", "Caspar", "Catharina", "Christian", "Christine", "Christof", "Christoph", "Clara", "Clemens", "Conrad", "Claus", "Camillo", "Claudia", + "Daniel", "Dietmar", "Dietrich", + "Edgar", "Edi", "Edith", "Edmund", "Egon", "Elsa", "Eduard", "Emmerich", "Erhard", "Erich", "Ernst", "Erwin", "Eugen", "Ewald", "Eberhard", + "Elisabeth", "Emanuel", "Emerich", "Emil", "Emma", "Engelbert", "Elias", + "Franz", "Ferdinand", "Friedrich", "Felix", "Florian", "Fritz", "Franziska", "Fred", + "Georg", "Gregor", "Gustav", "Gabriel", "Gebrüder", "Gabriele", "Gerhard", "Gerhart", "Gertrude", "Gottfried", "Gottlieb", "Grete", + "Günther", "Günter", + "Hans", "Hanns", "Heinrich", "Heinz", "Helmut", "Helmuth", "Hermann", "Herta", "Herbert", "Hubert", "Harald", "Horst", "Helene", "Helga", + "Hieronymus", "Hilde", "Hugo", + "Isabelle", "Ignaz", "Ilse", "Ingeborg", "Isidor", + "Josef", "Josefine", "Joseph", "Johann", "Johanna", "Johan", "Johannes", "Hannes", "Harry", "Jakob", "Jacob", "Jan", "Julian", "Julius", "Julia", "Jürgen", + "Karl", "Kasimir", "Konrad", "Klemens", "Klara", "Katharina", "Kurt", "Kaspar", "Klaus", "Koloman", "Konstantin", "Killian", + "Leopold", "Leo", "Lorenz", "Lothar", "Louise", "Ludmilla", "Ludwig", "Lukas", "Ladislaus", "Lambert", "Laurenz", "Liese", + "Lise", "Lilly", "Lina", "Lothar", "Lotte", "Luise", "Leonhard", "Lotte", "Martha", + "Maria", "Marie", "Michael", "Martin", "Max", "Maximilian", "Manuel", "Manuela", "Moritz", "Marianne", "Manfred", "Marc", + "Mathias", "Matthias", "Matthäus", + "Margarete", "Margarethe", + "Nico", "Niko", "Niklas", "Niclas", "Nikolaus", "Norbert", + "Otto", "Ottokar", "Oskar", "Olga", "Oswald", "Othmar", + "Paul", "Pauline", "Patrick", "Pepi", "Peter", "Philipp", "Philip", "Petrus", + "Robert", "Rudolf", "Richard", "Ruprecht", "Raffael", "Rolf", "Roman", "Rosa", "Rosalia", "Rupert", "Raimund", + "Sigmund", "Siegfried", "Simon", "Stanislaus", "Stefan", "Stephan", "Sepp", "Samuel", "Sebastian", "Susi", + "Theodor", "Thomas", "Theresia", "Thaddäus", "Theo", "Theophil", "Tina", "Titus", "Toni", "Trude", + "Ullrich", "Ulrich", "Udo", + "Viktor", "Vinzenz", "Victor", + "Wolfgang", "Willibald", "Wilhelm", "Willhelm", "Wilhelmine", "Walter", "Walther", "Wenzel", "Werner", "Willi", + "Zacharias"]; + + private static readonly HashSet LocalityNames = [ + "Groß-Enzersdorf", + "Groß Burgstall", "Klein Eberharts", "Klein-Engersdorf", "Groß-Enzersdorf", "Klein-Erlauf", + "Groß Gerungs", "Klein Gundholz", "Klein-Harras", "Klein-Heinrichschlag", "Groß-Höbarten", + "Klein-Jetzelsdorf", "Klein-Mariazell", "Groß Meinharts", "Klein-Meiseldorf", "Groß-Neusiedl", "Klein-Neusiedl", "Klein-Pöchlarn", + "Groß-Radischen", "Klein-Radischen", "Groß Raßberg", "Klein Reinprechts", "Klein-Reinprechtsdorf", "Groß-Reipersdorf", "Klein-Ruprechts", + "Groß-Schollach", "Klein-Schollach", "Groß-Schweinbarth", "Groß-Siegharts", "Klein-Siegharts", "Groß Sierning", + "Klein-Ulrichschlag", "Klein Weinberg", "Klein Wetzles", "Klein-Wien", + ]; + + private static bool IsGivenName(this string word) { + return StreetGivenNames.Contains(word); + } + + private static bool IsFamilyName(this string word) { + if (StreetLastNames.Contains(word)) + return true; + return false; + } + + private static bool IsPersonalName(this string word) { + return word.IsGivenName() || word.IsFamilyName(); + } + + private static bool IsGeographicalDerivativeName(this string word) { + if (!word.EndsWith("er") || word.IsPersonalName() || word == "Weißer") + return false; + return word.StartsWith("St. ") || word.StartsWith("Ober ") || word.StartsWith("Unter ") || word.StartsWith("Maria") || + word.EndsWith("sdorfer") || word.EndsWith("ndorfer") || word.EndsWith("ldorfer") || + word.EndsWith("felder") || + word.EndsWith("sthaler") || word.EndsWith("nthaler") || word.EndsWith("lthaler") || + word.EndsWith("dauer") || word.EndsWith("lauer") || word.EndsWith("mauer") || word.EndsWith("nauer") || word.EndsWith("ßauer") || + word.EndsWith("lbacher") || word.EndsWith("nbacher") || word.EndsWith("ßbacher") || + word.EndsWith("ringer") || word.EndsWith("singer") || + word.EndsWith("sbrunner") || word.EndsWith("nbrunner") || word.EndsWith("lbrunner") || word.EndsWith("abrunner") || word.EndsWith("ingbrunner") || + word.EndsWith("sburger") || word.EndsWith("nburger") || word.EndsWith("mburger") || word.EndsWith("lburger") || word.EndsWith("neuburger") || + word.EndsWith("berger") || + word.EndsWith("winkler") || + word.EndsWith("lsteiner") || word.EndsWith("nsteiner") || + word.EndsWith("städter") || + word.EndsWith("stättner") || word.EndsWith("stettner") || + word.EndsWith("brücker") || + word.EndsWith("further") || word.EndsWith("furter") || + word.EndsWith("aaer") || + word.EndsWith("seer") || word.EndsWith("leer") || + word.EndsWith("tzner") || + word.EndsWith("zeller") || + word.EndsWith("rther") || + word.EndsWith("sdner") || + word.EndsWith("gerner") || word.EndsWith("berner") || + word.EndsWith("frieder") || + word.EndsWith("litzer") || word.EndsWith("mitzer") || word.EndsWith("eutzer") || + word.EndsWith("loner") || + word.EndsWith("skirchner") || word.EndsWith("nkirchner") || word.EndsWith("ingkirchner") || + word.EndsWith("ziger") || + word.EndsWith("ließer") || + word.EndsWith("gerer") || + word.EndsWith("kreuzer") || + word == "Wiener" || word == "Brünner" || word == "Znaimer" || word == "Triester" || word == "Prager" || word == "Berliner" || word == "Basler" || + word == "Retzer" || word == "Tullner" || word == "Badner" || + word == "Gruber" || word == "Sulzer" || word == "Harraser" || word == "Dörfleser" || word == "Neusiedler" || + word == "Dornauer" || word == "Siebenhauser" || word == "Alser" || + word == "Bockenheimer"; + } + + public static string NormalizeAddress(string address, string? locality = null) { + var idx = address.IndexOf(','); + if (idx >= 0) address = address[..idx]; + var (name, nr) = Utils.SplitAddress(address); + return (NormalizeStreetName(name, locality) + " " + nr).Trim(); + } + + public static string NormalizeStreetName(string name, string? locality = null) { + name = name.Trim().ToLowerInvariant(); + if (name == "") { + return ""; + } else if (name == "nr.") { + if (locality == null) + return "Nr."; + name = locality.Trim().ToLowerInvariant(); + } + + name = name.Replace("hautp", "haupt") + .Replace("leop.", "leopold ").Replace("ferd.", "ferdinand ").Replace("gottfr.", "gottfried ") + .Replace("heinr.", "heinrich").Replace("joh.", "johann ").Replace("jos.", "josef ") + .Replace("msgr.", "monsignore ").Replace("pf.", "pfarrer ") + .Replace("präs.", "präsident "); + name = name.Replace("str.", "straße").Replace("strasse", "straße") + .Replace("bruecke", "brücke").Replace("pl.", "platz") + .Replace("gr.", "groß").Replace("kl.", "klein") + .Replace("sankt-", "st.").Replace("sankt ", "st.") + .Replace("wr.", "wiener ") + .Replace(".", ". "); + List words = [.. StreetNameWordsRegex.Split(name) + .Where(w => !string.IsNullOrWhiteSpace(w)) + .Select(w => StreetArticles.Contains(w) || StreetPrepositions.Contains(w) ? w : string.Concat(w[0..1].ToUpper(), w.AsSpan(1))) + .Select(w => w == "Ddr." ? "DDr." : w == "Lh" || w == "Osr" ? w.ToUpper() : w) + ]; + if (words[0] == "Di") words[0] = "DI"; + + var temp = "|" + string.Join('|', words) + "|"; + temp = temp.Replace("|Dipl.|Ing.|", "|DI|").Replace("|Dipl|Ing.|", "|DI|") + .Replace("|Kaiser|Ebersdorf|", "|Kaiser-Ebersdorf|") + .Replace("|Kalk|Deutz|", "|Kalk-Deutz|").Replace("|Kalk|Deutzer|", "|Kalk-Deutzer|") + .Replace("|Hohe|Wand|", "|Hohe Wand|").Replace("|Hohen|Wand|", "|Hohen Wand|").Replace("|Hohewand|", "|Hohe Wand|") + .Replace("|Alten|Markt|", "|Altenmarkt|") + .Replace("|Kirch|Hof|", "|Kirchhof|").Replace("|Bisc|Hof|", "|Bischof|") + .Replace("|Wiener|Neustadt|", "|Wiener Neustadt|").Replace("|Wiener|Neustädter|", "|Wiener Neustädter|") + .Replace("|St.|Pölten|", "|St. Pölten|").Replace("|St.|Pöltner|", "|St. Pöltner|") + .Replace("|Gross|", "|Groß|") + .Replace("|Nr|", "|Nr.|").Replace("|Und|", "|und|"); + words = [.. temp.Trim('|').Split('|')]; + + if (words.Count > 1 && (words[0] == "Groß" || words[0] == "Klein") && !StreetTypes.Contains(words[1])) { + var w = words[0] + words[1].ToLowerInvariant(); + words.RemoveRange(0, 2); + words.Insert(0, w); + } + if (words[0].StartsWith("Groß") || words[0].StartsWith("Klein")) { + var repl = LocalityNames.FirstOrDefault(n => n.Replace(" ", "").Replace("-", "").Equals(words[0], StringComparison.InvariantCultureIgnoreCase)); + if (repl != null) words[0] = repl; + } + + if (words.Count > 2 && words[^2] == "in") { + // remove suffix "in ..." + words.RemoveRange(words.Count - 2, 2); + } + + char sep = name.Contains('-') ? '-' : ' '; + if (words.Count <= 1) { + // single word + } else if (StreetTypes.Contains(words[0]) && !StreetTypes.Contains(words[^1])) { + // "Straße der ..." / "Straße des ..." + sep = ' '; + } else if (StreetTypes.Contains(words[^1])) { + // "... Straße" / "...straße" + var type = words[^1]; + var isCompound = true; + var isAdjective = StreetAdjectives.Contains(words[^2].ToLowerInvariant()); + var isGeographical = false; + var isPerson = false; + + var geo = words.FindLastIndex(IsGeographicalDerivativeName); + if (geo >= 0 && geo < words.Count - 2 && words[^2] != "Haupt" && words[^2] != "Bundes" && words[^2] != "Keller") { + isPerson = true; + isGeographical = true; + } else if (words.Any(w => w.Contains(' ') && !w.IsGeographicalDerivativeName())) { + isPerson = true; + isGeographical = true; + } else if (StreetNumbers.Contains(words[0])) { + isPerson = true; + isGeographical = true; + } else if (!StreetPrepositions.Contains(words[0]) && !StreetArticles.Contains(words[0]) && words.FindIndex(StreetPrepositions.Contains) > 0) { + isPerson = true; + isGeographical = true; + } + + if (words.Count(w => w.Contains('.')) >= 2 || words.Contains("van") || words.Contains("von") || words.Contains("St.") || + StreetPersonTitles.Contains(words[0]) || words.Any(w => w.IsGivenName())) + { + isPerson = true; + } + + if (!isPerson && isAdjective || StreetArticles.Contains(words[^2]) || StreetPrepositions.Contains(words[^2])) { + isCompound = false; + } + + if (!isPerson && words[^2].EndsWith("er") && !StreetLastNames.Contains(words[^2])) { + isGeographical = IsGeographicalDerivativeName(words[^2]); + } else if (words.Count >= 3 && words.Contains("Bad")) { + isPerson = true; + isGeographical = true; + } + + sep = isPerson ? '-' : ' '; + + if (isPerson && words.Count == 2 && !isGeographical) { + isPerson = false; + isCompound = true; + } + if (isGeographical || isPerson) { + isCompound = false; + } + if (isCompound) { + var w = words[^2] + type.ToLowerInvariant(); + words.RemoveRange(words.Count - 2, 2); + words.Add(w); + } + + if (isPerson) { + words = [.. words.Select(w => w.Replace(' ', '-'))]; + } + } else { + // unknown + } + + if (sep == '-' && StreetPrepositions.Contains(words[0]) && words[0] != "von" && words[0] != "van") { + sep = ' '; + int i; + for (i = 0; StreetPrepositions.Contains(words[i]) && i < words.Count; i++); + var word = string.Join('-', words.Skip(i)); + words = [.. words.Take(i).Append(word)]; + } + + var norm = string.Join(sep, words); + return string.Concat(norm[0..1].ToUpperInvariant(), norm.AsSpan(1)); + } + } +} diff --git a/Elwig/Helpers/Validator.cs b/Elwig/Helpers/Validator.cs index ef1b521..775104f 100644 --- a/Elwig/Helpers/Validator.cs +++ b/Elwig/Helpers/Validator.cs @@ -711,5 +711,25 @@ namespace Elwig.Helpers { return new(true, null); } + + public static ValidationResult CheckAddress(TextBox input, bool required, string? locality = null) { + var text = input.Text; + var (name, nr) = Utils.SplitAddress(text); + if (!string.IsNullOrWhiteSpace(nr)) { + var street = Normalizer.NormalizeStreetName(name.Replace(",", ""), locality); + var idx = input.CaretIndex; + input.Text = $"{street} {nr}".Trim(); + if (idx >= (text.Length - nr.Length)) { + input.CaretIndex = street.Length + (idx - (text.Length - nr.Length)) + 1; + } else if (idx > 0) { + input.CaretIndex = idx; + } + } + + if (text.Length == 0) + return required ? new(false, "Adresse ist nicht optional") : new(true, null); + + return new(true, null); + } } } diff --git a/Elwig/Resources/Sql/41-42.sql b/Elwig/Resources/Sql/41-42.sql index d73f107..aa96f58 100644 --- a/Elwig/Resources/Sql/41-42.sql +++ b/Elwig/Resources/Sql/41-42.sql @@ -8,8 +8,12 @@ ALTER TABLE wine_cultivation ADD COLUMN organic INTEGER NOT NULL CHECK (orga ALTER TABLE member_history RENAME COLUMN signed TO signed_member; ALTER TABLE member_history ADD COLUMN signed_company INTEGER NOT NULL CHECK (signed_company IN (TRUE, FALSE)) DEFAULT FALSE; -UPDATE member SET org_auth_code = UPPER(SUBSTR(a.name, -11, 10)) FROM member m JOIN member_billing_address a ON a.mgnr = m.mgnr WHERE a.name LIKE '%AT-BIO-___)'; -UPDATE member SET org_auth_code = UPPER(SUBSTR(a.name, -10, 10)) FROM member m JOIN member_billing_address a ON a.mgnr = m.mgnr WHERE a.name LIKE '%AT-BIO-___'; + +UPDATE client_parameter SET value = '0' WHERE param = 'ENABLE_TIME_TRIGGERS'; + +UPDATE member SET org_auth_code = UPPER(SUBSTR(a.name, -11, 10)) +FROM member m JOIN member_billing_address a ON a.mgnr = m.mgnr +WHERE m.mgnr = member.mgnr AND a.name LIKE '%AT-BIO-___)'; UPDATE member SET org_auth_code = UPPER(suffix) WHERE suffix LIKE 'AT-BIO-___'; UPDATE wine_cultivation SET organic = TRUE WHERE name LIKE '%bio%' OR name LIKE '%org%' OR description LIKE '%AT-BIO-%'; @@ -18,5 +22,18 @@ UPDATE wine_cultivation SET description = 'Biologische Produktion' WHERE organic UPDATE client_parameter SET param = 'CLIENT_ORGANIC_AUTHORITYCODE' WHERE param = 'CLIENT_ORGANIC_AUTHORITY'; + UPDATE member_telephone_number SET comment = NULL WHERE comment = ''; UPDATE member_email_address SET comment = NULL WHERE comment = ''; + +UPDATE member SET address = 'Untere Weißgerberstraße' WHERE address = 'U. Weißgasse Straße'; +UPDATE member SET address = 'Edgar-Penzig-Franz-Straße' WHERE address = 'Edgar-Penzing-Franz-Straße'; + +UPDATE member +SET address = NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member.postal_dest)) +WHERE address != NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member.postal_dest)); +UPDATE member_billing_address +SET address = NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member_billing_address.postal_dest)) +WHERE address != NORMALIZE_ADDRESS(address, (SELECT o.name FROM AT_plz_dest p JOIN AT_ort o ON o.okz = p.okz WHERE p.id = member_billing_address.postal_dest)); + +UPDATE client_parameter SET value = '1' WHERE param = 'ENABLE_TIME_TRIGGERS'; diff --git a/Elwig/Windows/AdministrationWindow.cs b/Elwig/Windows/AdministrationWindow.cs index 91b9565..9bba606 100644 --- a/Elwig/Windows/AdministrationWindow.cs +++ b/Elwig/Windows/AdministrationWindow.cs @@ -434,7 +434,7 @@ namespace Elwig.Windows { UpdateButtons(); } - protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) { + protected void TextBox_TextChanged(object sender, TextChangedEventArgs? evt) { var input = (Control)sender; var tb = input as TextBox; if (SenderIsRequired(input) && tb?.Text.Length == 0) { @@ -603,5 +603,13 @@ namespace Elwig.Windows { protected void AreaComTypeDetailsButton_Click(object sender, RoutedEventArgs? evt) { App.FocusBaseDataAreaComType(); } + + protected void AddressInput_TextChanged(object sender, TextChangedEventArgs? evt) { + if ((IsCreating || IsEditing) && sender is TextBox b) { + InputTextChanged(b, (i, r) => Validator.CheckAddress(i, r)); + } else { + TextBox_TextChanged(sender, evt); + } + } } } diff --git a/Elwig/Windows/BaseDataWindow.xaml b/Elwig/Windows/BaseDataWindow.xaml index 3ae254e..f63d626 100644 --- a/Elwig/Windows/BaseDataWindow.xaml +++ b/Elwig/Windows/BaseDataWindow.xaml @@ -90,7 +90,7 @@