Laden...

[erledigt] WPF-Textbox: TextChanged wird leider auch beim Binding ausgelöst

Erstellt von m.grauber vor 13 Jahren Letzter Beitrag vor 13 Jahren 11.076 Views
M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren
[erledigt] WPF-Textbox: TextChanged wird leider auch beim Binding ausgelöst

Hallo Experten,

in einer WPF-Form sitzen gebundene Textboxen ({Binding}).

In einem "Textbox_TextChanged-Event" wird ein bestimmter Code ausgeführt. Dieser Code soll aber nur dann ausgeführt werden, wenn der Benutzer Werte ändert (tippen per Tastatur, StrgX, StrgV, Copy&Paste per Rechtsklick, Drag&Drop von Text usw.)

Momentan wird das "TextChanged"-Event auch beim Laden der Form ausgeführt, da die Textbox ja gebunden {Binding} ist.

Nichts gebracht hat:

  • TextChanged-Event mit += erst am Ende des Window_Loaded zuweisen
  • oder dasselbe im Init-Event machen (wird ja noch früher aufgerufen)

Wo könnte ich das Event mit += zuweisen (gibt es noch etwas nach dem Window_Loaded-Event?) oder, wie könnte ich im TextChanged abfragen, ob die Änderung vom User oder per Binding vorgenommen wurde (in "sender" und "e" finde ich nichts passendes)

Die Lösung sollte natürlich nicht nur mit Textboxen, sondern auch anderen WPF-Controls funktionieren.

Vielen Dank!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

L
862 Beiträge seit 2006
vor 13 Jahren

Hallo,

Grundsätzlich ist es natürlich korrekt dass das TextChanged-Event immer dann ausgeführt wird wenn sich der Text ändert. Dafür ist das Event ja da. Soweit ich weis ist es auch nicht möglich an dieser Stelle zu prüfen woher die Textänderung kommt.

Vielleicht gibst du uns ja ein wenig Hintergrundinfos warum du mitbekommen möchtest ob der Benutzer es eingibt? Evtl. gibt es ja eine einfachere Variante dein Szenario zu lösen. Geht es zufällig um Validierung der Eingabe?

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Lector,

danke für die schnelle Antwort!

In einem Feld gibt es eine Fallnummer (z.B. ID01AD3FG42010)

diese Nummer soll standardmäßig aus den Eingaben verschiedener Textboxen zusammengesetzt werden. (z. B. 1. Buchstabe des Falls, Jahr etc.)

Nur wenn der Benutzer manuell in die Textbox mit der Fallnummer geht, soll er sie überschreiben und ändern können. Wenn sich später etwas an den Angaben ändert, soll die Fallnummer sofort neu "berechnet" werden.

Hat ein Benutzer also eine Fallnummer manuell geändert und ich öffne die Maske mit dem Fall, wird dummerweise jedesmal die Berechnung durchgeführt, obwohl die Änderung nur auf dem Binding beruht.

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

L
862 Beiträge seit 2006
vor 13 Jahren

Ich empfehle jetzt mal folgende Vorgehensweise:

Mach ein ViewModel (falls nicht schon vorhanden) in dem es folgende Properties gibt:

  • Fallnummer
  • Die ganzen anderen Sachen aus denen sich die Fallnummer errechnen lässt
    (- sonstiges)

In der GUI bindest du sämtliche Eingabefelder per TwoWayBinding an das ViewModel. Somit kannst du ausserhalb der GUI schon mal alles steuern.

Die Fallnummer belegst du dann so:

Wenn sich in deinem ViewModel die Fallnummer einfach so ändert lässt du diese Änderung einfach zu und machst nichts.
Wenn sich in einem Property, weches zur Berechnung der Fallnummer benutzt wird, etwas ändert dann errechnest du die Fallnummer anhand der Properties neu. Wichtig ist dass du im setter prüfst ob sich auch der Wert auch WIRKLICH geändert hat. Durch dein Binding ist es ja möglich dass das Jahr von 2010 erneut auf 2010 gesetzt wird und dadurch die Fallnummer neuberechnet wird obwohl der Benutzer diese vorhin bereits manuell eingegeben hat. Dieses Verhalten sollte man durch eine Content-Prüfen im Setter der Properties verhindern können.
Bei DependencyProperties hast du dieses Verhalten übrigens nicht da diese von Haus aus besagte Content-Prüfung mitbringen.

Ich hoffe das hat dir geholfen.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Lector,

vielen Dank für die sehr gute und auch richtige Schilderung! Bitte mich jetzt nicht umbringen:

Ich weiß, wie Sie die Änderung machen möchten und umgehen dies geschickt, indem Sie die Berechnung in das Viewmodel verlagern - dahin, wo es auch eigentlich hingehört.

Leider arbeite ich (noch) nicht mehrschichtig und zudem über das Entity Framework, bei denen ich direkt an die Daten gebunden habe. Außerdem sollen die Änderungen direkt beim Tippen neu berechnet werden (Wunsch vom Kunden).

Gibt es noch einen nicht "so sauberen" Weg, um die Angaben im Formular zu berechnen?

Wenn ich auf der Form einen Button einfüge, und in dessen Click-Event das Textbox_TextChanged-Event mit += an meine Neuberechnung "kopple", funktioniert die Aktualisierung nach dem Drücken des Buttons wie gewünscht. Nur ich muss manuell vorher 1x nachdem die Form offen ist auf den Button klicken. Ich möchte auch nicht zu sehr "pfuschen" und einen Timer verwenden.

Gibt es vielleicht noch eine Lösung?

Ich hoffe auf dein Verständnis!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

S
489 Beiträge seit 2007
vor 13 Jahren

Gibt es noch einen nicht "so sauberen" Weg, um die Angaben im Formular zu berechnen?

Nein. Das über ein ViewModel zu machen ist der saubere Weg. In der Codebehind Datei des Window hat meiner Meinung nach kein fitzelchen Code irgendwas zu suchen so lange es nicht anders möglich ist.

Außerdem sollen die Änderungen direkt beim Tippen neu berechnet werden (Wunsch vom Kunden).

Das geht mit dem ViewModel bestens. Setze im Text-Binding den UpdateSourceTrigger auf PropertyChanged.

L
862 Beiträge seit 2006
vor 13 Jahren

Ich würde an deiner Stelle so vorgehen:

**Bauen ein ViewModel welches auch als Model agiert. Dadurch sparst du dir schon mal eine Menge Aufwand. Du baust dir also eine Klasse die NUR die ganzen Properties hat und INotifyPropertyChanged feuert.

Wenn du willst dass sich das Binding bei der Texteingabe live aktialisiert kannst du einfach den UpdateSourceTrigger auf PropertyChanged setzen. Dieser steht bei TextBoxen per Default auf LostFocus.**

Somit kannst du ohne viel Aufwand schon mal dein Wunsch-Szenario realisieren.

Den Weg über Timer und Überprüfung wer den jetzt Property XY geändert hat würde ich an deiner Stelle nicht gehen da es warscheinlich ähnlich viel Aufwand ist da am Schluss wieder irgendwas nicht geht. Und irgendwann musst du eh alles rückgängig und sauber machen weil das Projekt doch sehr komplex wird.
Zumindest habe ich bei meinen Projekt diese Erfahrung gemacht dass solche Hacks nicht wirklich was bringen

Wenn du ein Dummy-ViewModel wie oben beschrieben machst hast du zumindest mal eine saubere Schnittstelle zur GUI. Die Trennung zwischen ViewModel und Model kannst du dann später immer noch nachziehen wenn du mal Zeit hast. An der GUI musst du dann zumindest nichts mehr ändern.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo SeboStone,

ich habe mich etwas verkehrt ausgedrückt: Die Funktion zur Berechnung steht natürlich nicht in der CodeBehind-Datei, sondern ist von allen Programmteilen zentral aufrufbar.

Nur arbeite ich leider nicht mehrschichtig über ein ViewModel, sondern mit dem EF.

Wegen dieser Änderung möchte ich auch (noch) nicht komplett alles umschreiben müssen.

Irgendwie muss so etwas ansich relativ einfaches doch möglich sein?

Danke vielmals!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Lector,

der Timer könnte dennoch sehr einfach ausgelöst werden:

Z.B. Im Window_Loaded() Timer starten:
50 ms nach dem Loaded wird der Timer nur einmal ausgeführt
Im Timer wird nur einmalig das "Textbox_TextChanged-Event" mit += an meine gewünschte Methode gebunden (die die Fallnummer berechnet).

Danach läuft alles schon so, wie bereits gewünscht, da das {Binding} vom Laden bereits abgeschlossen ist, ändert sich das TextChanged wirklich nur dadurch, wenn der Benutzer etwas eingibt.

Der Timer verzögert den Code im Timer nur etwas. Wenn es eine Methode Window_AfterBinding oder Window_AfterLoading gäbe, könnte man den Code auch dort hineinschreiben, und würde keinen Timer benötigen.

Das separate ViewModel kommt mir ziemlich aufwändig vor. Ich hoffe halt immer noch, mich darum zu "drücken". 🙁

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

S
489 Beiträge seit 2007
vor 13 Jahren

Timer? Das ist nicht Dein Ernst! 🙄

Die Zeit die Du in solche Frickeleien steckst ist besser angelegt einfach noch ein VM einzuziehen. Du wirst im Laufe Deinen Projektes noch auf weitere solche Probleme stoßen und dann geht die Frickelei von vorne los. Der Aufwand für neue Features wird sich mit jeder Frickelei potenzieren. Ebenfalls wird die Komplexität immer weiter steigen. Über die Kosten sprechen wir lieber erst gar nicht. Und schlussendlich wirst Du irgendwann ein Redesign machen müssen. Und rate mal was Du dann implementierst ... genau, ein VM. Spar Dir das doch lieber gleich.

L
862 Beiträge seit 2006
vor 13 Jahren

Wie ich mit WPF angefangen habe habe ich mich auch sehr lange vor ViewModels gedrückt weil ich zuerst der Meinung war dass ein solcher Wrapper unsauber ist. Ich glaube ich hab damals jede andere erdenkliche Lösung einmal ausprobiert bin aber nun mittlerweile doch bei ViewModels geladet die ich nun schon sehr lange einsetze und bin mit dieser Architektur ziemlich zufrieden.

Ich kenne mich mit dem Entity-Framework leider überhaupt nicht aus. Um ehrlich zu sein weis ich gar nicht mal was das eigendlich ist.

Ich kann dir allerdings versichern dass das mit den ViewModels gar nicht mal so aufwändig ist wie man am Anfang denkt. Für den Start musst du ja nicht mal eine Trennung zwischen ViewModels und Models machen. Einfach Properties rein, Changed-Events feuern und fertig ist das Grundgerüst. Evtl. kannst du ja in deiner bestehenden Klasse (ich nehme an du hast bereits eine da du die Berechnung ja nicht in der GUI machst) einfach INotifyPropertyChanged implementieren. Deine TextBoxen scheinen ja momentan auch per Binding an irgendeinem Objekt zu hängen.

S
489 Beiträge seit 2007
vor 13 Jahren

Wäh, da habe ich den Lector mit dem m.grauber -> löschen 🙂

L
862 Beiträge seit 2006
vor 13 Jahren

Wie ich mit WPF angefangen habe habe ich mich auch sehr lange vor ViewModels gedrückt weil ich zuerst der Meinung war dass ein solcher Wrapper unsauber ist. Ich glaube ich hab damals jede andere erdenkliche Lösung einmal ausprobiert bin aber nun mittlerweile doch bei ViewModels geladet die ich nun schon sehr lange einsetze und bin mit dieser Architektur ziemlich zufrieden.

Du setzt doch gar keine ViewModels ein, Du bindest doch direkt an deine reale Datenstruktur - das ist was anderes.

Nein in meine Code habe ich tatsächlich "echte" ViewModels als Wrapper um die reale Datenstruktur (Models).

Die Verschmelzung von VM und Model habe ich nur als 'schnelle' Lösung genannt da es sich so anhört als würde m.grauber etwas unter Zeitdruck stehen. Das sollte nicht viel länger dauern als ein Timer. Dafür ist es viel sauberer Code aus dem sich dann irgendwann mal ein 'echtes' VM entwickeln kann.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Lector und SeboStone,

ja, in der Tat stehe ich - natürlich wie alle unter Zeitdruck. Ich weiß auch, dass Ihr mit der Umsetzung per ViewModel recht habt!

Ich habe es vorerst (unsauber) so gelöst:

In PreviewKeyDown wird "Textbox_TextChanged-Event" mit += an meine gewünschte Methode gebunden. (Dieser Code wird nur 1x ausgeführt)

Danach klappt es.

Trotzdem werde ich es mir ansehen und eine ViewModel-Schicht "einzuziehen" versuchen. Ihr habt mich hier wirklich in die richtige Richtung angestoßen. 😃

Vielen Dank für Eure tolle Unterstützung!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]

W
1 Beiträge seit 2010
vor 13 Jahren

Zitat von m.grauber:
Dieser Code soll aber nur dann ausgeführt werden, wenn der Benutzer Werte ändert ...

Vielleicht einfach im Event "Textbox_TextChanged-Event" CaretIndex >0 abfragen.

M
m.grauber Themenstarter:in
343 Beiträge seit 2010
vor 13 Jahren

Hallo Wpffan,

Danke für den Tipp! 👍

Darauf bin ich nicht gekommen. Das klappt!

Vielen Dank!

Mfg
Michael

PS: Ich stelle nur Fragen, wenn ich in Büchern, im Web und in Foren nichts gefunden habe. Dumme Fragen bitte ich zu entschuldigen!

:] VISUAL STUDIO 2017 + .NET FRAMEWORK 4.5 + SQL-Server 2012 :]