Entwickler-Ecke
WinForms - Button Enabled wenn Textboxen geändert werden
Talemantros - Di 25.03.14 21:04
Titel: Button Enabled wenn Textboxen geändert werden
Hallo,
ich habe ein Form mit 3 TextBoxen und einen Button der nur gedrückt werden kann, wenn die Daten vorher gespeichert werden.
Daher möchte ich, wenn jemand eine TextBox ändert nur den Speicherbutton anzeigen lassen und erst wenn dieser gedrückt wurde den anderen.
Muss ich jetzt für jede TextBox ein TextChanged schreiben und eine boolVariable setzen?
Oder gibt es da einen anderen Ansatz?
Vielen Dank
Gruß
Daniel
Ralf Jansen - Di 25.03.14 23:46
Wo kommen die Daten her? Wenn ich eine Modelklasse hinter den Controls annehmen darf dann sollte die Klasse einfach ihren eigenen Änderungstatus nachhalten und als Property mit passendem PropertyChanged Event veröffentlichen so das man den Button einfach an die Modelklassen Property binden kann.
Talemantros - Mi 26.03.14 09:16
Hallo Ralf,
danke für deine Antwort, aber das war zu viel für mein Anfänger Hirn :-(
Also in dem akuten Fall habe ich die TextBoxen durch Abfragen aus der MDF gefüllt mit dem SQL Statement?!
Wärst du bereit es mir verständlicher näher zu bringen?
Sorry!
Gruß
Daniel
Edit:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| private void optionenSystem_Load(object sender, EventArgs e) { try { txtServer.Text = localDbQuery.getOptions(dbInfo.getConnStr(), 5, 2); txtBenutzer.Text = localDbQuery.getOptions(dbInfo.getConnStr(), 2, 2); txtPassword.Text = localDbQuery.getOptions(dbInfo.getConnStr(), 3, 2); txtDatenbank.Text = localDbQuery.getOptions(dbInfo.getConnStr(), 4, 2); lblPfadVorlagen.Text = localDbQuery.getOptions(dbInfo.getConnStr(), 6, 2); } catch (Exception ex) { msgAusgabe.showError(ex.Message); throw; }
} |
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| public static string getOptions(string connStr, int idAbfrage, int idRueckgabe) { string rueckgabe = string.Empty; SqlConnection conn = new SqlConnection(connStr);
conn.Open(); using (SqlCommand command = new SqlCommand("SELECT * from tblOptionen WHERE id =@id", conn)) { command.Parameters.AddWithValue("@id", idAbfrage); SqlDataReader reader = command.ExecuteReader();
while (reader.Read()) { rueckgabe = reader.GetString(idRueckgabe); } } conn.Close();
return rueckgabe; } |
Aus der dbInfo kommt nur der ConnectionString
Gruß
Daniel
Th69 - Mi 26.03.14 10:29
Hallo Talemantros,
du kannst auch alle 3 Textboxen mit derselben Ereignismethode verknüpfen (geht auch schon im VS-Designer), so daß du dann nur
einen Code hast:
C#-Quelltext
1: 2: 3: 4:
| void TextBox_TextChanged(object sender, EventArgs e) { buttonSave.Enabled = true; } |
Besser wäre aber wirklich der Ansatz von Ralf, d.h. die Trennung von GUI und Logik (geschweige denn von Datenbank-Operationen).
Erstelle dir zuerst einmal eine eigene Modellklasse, so daß dein GUI-Code nur noch diese benutzt (d.h. alle DB-Operationen auch auslagern) und dann kannst du mittels der
Control.DataBindings-Eigenschaft [
http://msdn.microsoft.com/de-de/library/system.windows.forms.control.databindings%28v=vs.110%29.aspx] die Verknüpfung(en) herstellen.
Talemantros - Mi 26.03.14 14:19
Hallo zusammen,
vielen Dank
Da ich ja immer offen bin wie man es besser macht und richtiger würde mich den Ansatz gern versuchen, habe aber meine Schwierigkeiten damit.
Ich habe bisher in meinen Posts nicht danach gefragt, aber wäre es in dem Fall vielleicht möglich mir eine kleine Beispieldatei zu bauen?
Sorry, aber ich stehe auf dem Schlauch!? Es fehlt schon am Verständnis der Modellklasse.
Jetzt stehe ich noch am Anfang meines Tools und wäre sinnig es gleich richtig zu machen.
Vielen dank
Gruß
Daniel
Th69 - Mi 26.03.14 15:08
Hallo Talemantros,
du könntest dir mal mein PersonManagement-Projekt von
Kommunikation von 2 Forms [
http://www.bitel.net/dghm1164/programming/Kommunikation_von_2_Forms.html] (Link ist ganz unten auf der Seite) anschauen. Dort habe ich zwar keine Datenbank-Anbindung, aber die Modell-Klassen sind unter "Logic" zu finden (obwohl es bisher nur reine Datenhaltungsklassen sind, aber zusätzliche Logikmethoden würde man dann dort implementieren).
Für das Einbinden der Datenbankfunktionalität (entsprechend der
Drei-Schichten-Architektur [
http://de.wikipedia.org/wiki/3-Schichten-Architektur#Drei-Schichten-Architektur]) am besten auch einen eigenen Unterordner mit Klassen anlegen (für größere Projekte würde man diese Unterordner dann sogar in eigene Projekte, d.h. Assemblies, auslagern) - und dann entweder von den Modellklassen darauf zugreifen oder aber eine Service-Klasse erstellen, welche die Modellobjekte mit den Daten aus der DB füllt).
Das Verwenden der Modellklasse wird z.B. dann in der Controls/PersonUserControl gezeigt.
Ich hoffe, damit kommst du weiter und verstehst damit mehr den Sinn einer verteilten Anwendung.
P.S. Ein guter Anhaltspunkt für die Entscheidung, was in die GUI und was in die Logikschicht kommt, ist es, wenn man sich überlegt, für sein Projekt sowohl eine WinForms-GUI als auch eine Konsolenanwendung zu erstellen - alle gemeinsamen Funktionalitäten kommen dann in die Logikschicht (Modelle oder Services etc.).
Talemantros - Mi 26.03.14 15:26
Hallo TH69,
vielen dank für den Post.
Ich werde versuchen mich heute oder morgen damit zu beschäftigen und zu verstehen.
Vielen Dank
Gruß
Daniel
Ralf Jansen - Mi 26.03.14 15:32
Ein vollständiges Projekt wie von
Th69 ist sicher am sinnvollsten da man das auch in Aktion sehen kann. Da ich anhand deines Codes was gebastelt habe poste ich das mal trotzdem.
a.) Ein Klasse die deine Optionen hält für DataBinding vorbereitet ist und den Änderungsstatus (Modified) nachhält.
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102:
| public class ConnectionOptions : INotifyPropertyChanged { public ConnectionOptions(string server, string benutzer, string password) { this.server = server; this.benutzer = benutzer; this.password = password; this.modified = false; }
private bool modified; public bool Modified { get { return modified; } set { if (value != modified) { modified = value; OnModifiedChanged(); NotifyPropertyChanged(); } } }
private string server = String.Empty; public string Server { get { return server; }
set { if (value != server) { server = value; Modified = true; NotifyPropertyChanged(); } } }
private string benutzer = String.Empty; public string Benutzer { get { return benutzer; }
set { if (value != benutzer) { benutzer = value; Modified = true; NotifyPropertyChanged(); } } }
private string password = String.Empty; public string Password { get { return password; }
set { if (value != password) { password = value; Modified = true; NotifyPropertyChanged(); } } }
public event EventHandler ModifiedChanged; private void OnModifiedChanged() { var changed = ModifiedChanged; if (changed != null) changed(this, EventArgs.Empty); }
public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { var changed = PropertyChanged; if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName)); } } |
b.) Ein neue Methode die die Optionen in der neuen Klasse verpackt
C#-Quelltext
1: 2: 3: 4: 5: 6:
| public static ConnectionOptions getConnectionOptions(string connStr) { return new ConnectionOptions(localDbQuery.getOptions(dbInfo.getConnStr(), 5, 2), localDbQuery.getOptions(dbInfo.getConnStr(), 2, 2), localDbQuery.getOptions(dbInfo.getConnStr(), 3, 2)); } |
c.) Ein Überarbeitung deines Load Events das nun diese Klasse verwendet
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15:
| private ConnectionOptions options; private void optionenSystem_Load(object sender, EventArgs e) { if(options != null) options.ModifiedChanged -= options_ModifiedChanged; options = getConnectionOptions(dbInfo.getConnStr()); options.ModifiedChanged += options_ModifiedChanged;
txtServer.DataBindings.Add("Text", options, "Server"); txtBenutzer.DataBindings.Add("Text", options, "Benutzer"); txtPassword.DataBindings.Add("Text", options, "Password"); } |
d.) einen Eventhandler (oben in Load schon verdrahtet) um das andern von Modified der Klasse abzufangen und deinen Save Button passend zu setzen
C#-Quelltext
1: 2: 3: 4:
| void options_ModifiedChanged(object sender, EventArgs e) { meinLieberButtonForSaving.Enabled = options.Modified; } |
Talemantros - Mi 26.03.14 16:29
Wow, vielen Dank euch beiden.
Bevor ich weiter baue, werde ich mir beides ausführlich anschauen und versuchen zu verstehen.
Vielen Dank
Gruß
Daniel
P.S.: Immer wenn ich denke ich habe was verstanden und bin ein Stück weiter, bekomme ich (positiv gesehen) eins zwischen die Hörner :-)
Talemantros - Do 27.03.14 11:18
Guten Morgen,
ich habe nun mal den Code von Ralf durchgearbeitet, hänge allerdings bei vielen Ecken und bin ein wenig frustriert.
Das Projekt von Th69 konnte ich mir nur grob anschauen, da dort einige Fehler bei mir gezeigt werden. Aber die Übersichtlichkeit der Modellklassen etc sind mir erst mal bewusst geworden. Dies werde ich versuchen bei mir auch so umzusetzen.
Es ergeben sich noch einige Fragen für mich:
1. Die Klasse public class ConnectionOptions erbt von INotifyPropertyChanged. Gehe ich recht in der Annahme, dass dies eine C# eigene Klasse ist, die speziell solche Dinge abfängt?
2.) Die Initialisierung des Konstruktors mit den Parametern und die Propertys verstehe ich soweit. Leider komme ich mit den Eventhandlern gar nicht zurecht.
Der erste Eventhandler setzt True oder False bei Änderungen der Propertys. Was genau macht dabei changed(this, EventArgs.Empty);
3.) Die Aufgabe des 2 EventHandlers und deren Übergabewerte ([CallerMemberName] String propertyName = "") verstehe ich leider gar nicht.
Ich werde mal versuchen dies in mein Projekt zu basteln, ein wenig mit rumspielen in der Hoffnung das Verständnis wird mir klar.
Danke schon mal...
frustrierte Grüße :-)
Daniel
EDIT: Bin gerade auf
http://www.mycsharp.de/wbb2/thread.php?threadid=26116 gestoßen, welches ich mir dann auch parallel noch mal anschauen werde
Ralf Jansen - Do 27.03.14 11:45
Zitat: |
1. Die Klasse public class ConnectionOptions erbt von INotifyPropertyChanged. Gehe ich recht in der Annahme, dass dies eine C# eigene Klasse ist, die speziell solche Dinge abfängt? |
Wie das I am Anfang andeutet ist es ein Interface keine Klasse. Es stellt im Prinzip nur das PropertyChanged Event bereit das zum automatischen Daten synchronisiern zwischen Controls und Klassen benutzt wird. Wie du im Code siehst wird das PropertyChanged in den Settern der Properties verwendet. Sobald sich also eine Property ändert wird dieses Event geworfen. Die Textboxen sind per Databinding an diese Klasse gebunden und horchen auf das PropertyChanged Event.Heißt also sobald sich die Klasse ändert ändert sich auch die Anzeige in den Textboxen ohne das man da manuell eingreifen muß.
Zitat: |
2.) Die Initialisierung des Konstruktors mit den Parametern und die Propertys verstehe ich soweit. Leider komme ich mit den Eventhandlern gar nicht zurecht.
Der erste Eventhandler setzt True oder False bei Änderungen der Propertys. Was genau macht dabei changed(this, EventArgs.Empty); |
Es wirft den PropertyChanged Event. So das jemand anderes den fangen kann. Du kennst das mit den Events vermutlich nur vom anderen Ende her also das du Events fängst und behandelst wie zum Beispiel in deinem Code das Load Event der Form. Aber die Form muss denn ja auch irgendwie ausführen und das macht es mit Load ähnlich wie ich hier mit Modified oder dem PropertyChanged Event.
Zitat: |
Die Aufgabe des 2 EventHandlers und deren Übergabewerte ([CallerMemberName] String propertyName = "") verstehe ich leider gar nicht. |
Das CallerMemberName Attribut ersetz den Inhalt des propertyName Parameters durch den Methoden/Propertynamen der Methode der diese Methode aufgerufen hat.
Im Setter der Password Property hätte ich die Methode z.B auch so
NotifyPropertyChanged("Password"); aufrufen können das wäre aber identisch zu dem was jetzt passiert da das Attribut automatisch den string "Password" in propertyName schreibt. Wenn ich nicht das Attribut verwenden würde besteht das Risiko das bei Ändern des Propertynamen vergessen wird diesen String mit anzupassen. Wenn wir z.B. Password in Kennwort umbennnen aber vergessen den String mit zu ändern würde beim Ändern der dann Kennwort genannten Property ein PropertyChanged Event ausgelöst werden der behauptet die (nicht existente) Passwort Property wurde geändert. Für einen Programmierer wäre das Unfug und das autmatische Databinding würde nicht mehr funktionieren. Wenn ich eine Kennwort Property an ein Control binde horcht das Control natürlich auf einen Event der besagt das Kennwort geändert wurde und nicht Password.
Deine 3 Fragen drehe sich alle um die Implementierung der Klasse damit sie im Context von Databinding funktioniert. Wen du kein Databinding möchtest kannst du das auch ausbauen dann bleibt recht wenig von der Klasse übrig ;)
Talemantros - Do 27.03.14 11:53
Hallo Ralf,
Vielen Dank für die ausführliche Antwort.
Werde mich damit gleich weiter beschäftigen. Zum Glück ist heute nicht so viel an der Arbeit los :-)
Das mit dem Databindings würde ich gern so nutzen, aber auch da muss ich noch mal schauen was du mit dem Kommentar über den Designer meintest.
Werde ich dann aber versuchen wenn der Rest verständlich im Kopf ist. Leider fühlt es ich so an, als ob ich zu blöd bin und zu langsam verstehe :-(
Aber wenn man gefühlt ganz unten ist geht es ja nur noch nach oben :-)
Viele liebe Grüße
Daniel
P.S.: Habe wie oben im Edit erwähnt noch etwas zu Events gefunden, was ich mir heute Abend auf der Couch noch zu Gemüte führen werden.
Talemantros - Sa 29.03.14 23:00
Hallo Ralf,
habe mich jetzt einige Zeit damit beschäftigt und alles Schritt für Schritt auseinander der genommen und denke ich komme so weit klar.
Was ich mich jetzt noch frage ist:
1.) in den Tutorials und Büchern werden Events (immer) mit einem delegate erklärt!
Wieso ist das bei dir nicht so? Wann macht man es mit delegate.
2.) Für was genau ist das Databinding. Wenn ich nur ein Form gebe in dem die Dazen geladen und geändert werden brauch ich es dann? Da es ja von außen nicht geändert werden kann? Oder verstehe ich das ganz falsch?!
3.) Im Projekt von Th69 sind die forms mit UserControls bestückt. Haben diese noch andere Vorteile außer der Wiederverwendbarkeit in anderen Projekten?
Vielen Dank und sonniges Wochenende
Gruß
Th69 - So 30.03.14 11:38
Hallo (bin zwar nicht Ralf, aber ich hoffe, meine Antworten nimmst du auch ;-)
zu 1)
Ein Event ist im Grunde nur ein Delegate, jedoch mit speziellen Einschränkungen.
Ein eigenes
delegate wird (bzw. sollte) für Events nicht mehr verwendet (werden), da es im Framework die Klasse
EventHandler [
http://msdn.microsoft.com/en-us/library/system.eventhandler.aspx] bzw. die generische Version
EventHandler<TEventArgs> [
http://msdn.microsoft.com/de-de/library/db0etb8x%28v=vs.110%29.aspx] davon gibt. Der Vorteil dabei ist, daß dann Events immer nach dem gleichen Schema
(object sender, EventArgs e) aufgebaut sind.
zu 2)
Databinding ist einfach die bequeme Art, Daten mit Controls zu verbinden, ohne daß der Entwickler jeweils von Hand diesen Code (Daten zuweisen, auslesen und evtl. konvertieren) schreiben muß (in WPF ist dies halt der Standard und auch in WinForms ist dies zu empfehlen).
Ob das DataBinding nur intern oder über eine öffentliche Schnittstelle passiert, spielt dabei keine Rolle.
zu 3)
UserControls bzw. auch selbstentwickelte Controls dienen dem Kapselungsprinzip (und damit auch der Wiederverwendbarkeit). Gerade Anfänger neigen dazu eine Form mit Dutzenden von Einzelelementen zu bestücken (im Sinne von labelX, textBoxX, buttonX) - der bessere Weg ist dann die Entwicklung
eines (User)Controls, welches diese Elemente aufnimmt und man dann eine explizite Schnittstelle für den Zugriff bereitstellt!
Ich verwende fast immer ein UserControl (bzw. bei grafischen Controls ein
Panel) für die Hauptanzeige innerhalb eines Forms, denn dann kann man diese Form noch sehr bequem um weitere Elemente ergänzen (z.B. eine Menü- oder Toolbar), ohne die vorhandenen Elemente (negativ) zu beeinflussen.
Dies reduziert den Code innerhalb einer Form und macht daher den gesamten Code viel übersichtlicher (in der Form sind dann nur noch Initialisierung sowie die nötigen Event-Methoden enthalten).
Talemantros - So 30.03.14 17:44
Hey Th69,
Klar nehme ich auch gern deine Antwort entgegen.
Wollte keinen ausschließen. Sorry
Ich hätte es auch wie Anfänger gemacht und viele Steuerelemente genutzt.
Gut dass vieles wie die Modellklassen und UserControls jetzt am Anfang aufkamen. So kann ich es gleich anständig lernen/ bauen.
Werde mich noch weiter mit den Databindings und Controls beschäftigen und dann mal weiter basteln. Bin gespannt.
Vielen Dank
Viele Grüße
Talemantros - Mo 31.03.14 19:49
Guten Abend,
es tut mir sehr leid, dass ich jetzt noch mal fragen muss.
Nachdem ich nun einige Tutorials und Dokumentationen gelesen hatte und mit Hilfe eurer Antworten, dachte ich alles verstanden zu haben.
Nun wollte ich dies in mein Projekt einbauen:
Die Aufgabenstellung war ja, dass der Button "Verbindung testen" erst nach dem Speichern "enabled = true" ist und bei Änderung der Textfelder aus "false" wechselt.
Nun habe ich die Modellklasse noch um eine Property "Datenbank" erweitert (die Table in der MySQl Datenbank)
Die Databindung habe ich noch nicht anhand des Kommentars von Ralf umgebaut, weil ich erstmal wollte, dass es so funktioniert und ich es dann im nächsten Step umbaue.
Ich gehe halt lieber langsam ran und verstehe dann aber was ich da tue.
Mein Problem sehen nun wie folgt aus:
Wenn ich was in den Textfeldern ändere geht der Button "Verbindung testen" auf false, so wie gewünscht und wenn ich dann auf Speichern drücke wieder auf true. Leider nur einmal nach dem öffnen der Form. Bei jeder weiteren Änderung tut sich nichts und ich verstehe nicht wieso.
Wäre wie immer über Hilfe sehr dankbar!
Code der Klasse
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146:
| using localDbConnect; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks;
namespace rEcycle.Logics { public class connectionOptions : INotifyPropertyChanged { private bool modified; private string server = string.Empty; private string benutzer = string.Empty; private string password = string.Empty; private string datenbank = string.Empty; public event EventHandler ModifiedChanged; public event PropertyChangedEventHandler PropertyChanged;
public connectionOptions(string server, string benutzer, string password, string datenbank) { this.server = server; this.benutzer = benutzer; this.password = password; this.datenbank = datenbank; this.modified = false; }
private void OnModifiedChanged() { var changed = ModifiedChanged; if (changed != null) { changed(this, EventArgs.Empty); } }
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { var changed = PropertyChanged; if (changed != null) { changed(this, new PropertyChangedEventArgs(propertyName)); } }
#region Properties
public bool Modified { get { return modified; } set { if (value != modified) { modified = value; OnModifiedChanged(); NotifyPropertyChanged(); } } }
public string Server { get { return server; } set { if (value != null) { server = value; Modified = true; NotifyPropertyChanged(); } } }
public string Benutzer { get { return benutzer; } set { if (value != null) { benutzer = value; Modified = true; NotifyPropertyChanged(); } } }
public string Password { get { return password; } set { if (value != null) { password = value; Modified = true; NotifyPropertyChanged(); } } }
public string Datenbank { get { return datenbank; } set { if (value != null) { datenbank = value; Modified = true; NotifyPropertyChanged(); } } }
#endregion } } |
Code der Form (bisher noch ohne ausgelagerte UserControl // einfach nur TextBoxen und Buttons zum Test)
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67:
| using rEcycle.Logics;
namespace rEcycle { public partial class optionenSystem : Form { private connectionOptions options;
public optionenSystem() { InitializeComponent(); }
private void optionenSystem_Load(object sender, EventArgs e) {
if (options != null) options.ModifiedChanged -= options_ModifiedChanged; options = getConnectionOptions(dbInfo.getConnStr()); options.ModifiedChanged += options_ModifiedChanged;
txtServer.DataBindings.Add("Text", options, "Server"); txtBenutzer.DataBindings.Add("Text", options, "Benutzer"); txtPassword.DataBindings.Add("Text", options, "Password"); txtDatenbank.DataBindings.Add("Text", options, "Datenbank"); }
public static connectionOptions getConnectionOptions(string connStr) { return new connectionOptions(localDbQuery.getOptions(connStr, 5, 2), localDbQuery.getOptions(connStr, 2, 2), localDbQuery.getOptions(connStr, 3, 2), localDbQuery.getOptions(connStr, 4, 2)); }
private void options_ModifiedChanged(object sender, EventArgs e) { btnVerbTest.Enabled = false; }
private void btnVerbTest_Click(object sender, EventArgs e) { mysqlDbQuery.connectTest(dbInfo.getMysqlConnStr()); }
private void btnSpeichern_Click(object sender, EventArgs e) { try { localDbQuery.setOptions(dbInfo.getConnStr(), 2, txtBenutzer.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 3, txtPassword.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 4, txtDatenbank.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 5, txtServer.Text);
msgAusgabe.showInformation("Die Daten wurden erfolgreich gespeichert"); } catch (Exception ex) { msgAusgabe.showError(ex.Message); throw; }
btnVerbTest.Enabled = true; } } } |
EDIT: Ausgehend von der Modelklasse, die ich nun im Hintergrund liegen habe, würde man den Speicher Button dann noch so lassen? Oder eher anders?
Hinter dem Aufruf liegt:
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| public static void setOptions(string connStr, int id, string inhalt) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("UPDATE tblOptionen SET inhalt=@inhalt WHERE Id=@id", conn)) { cmd.Parameters.AddWithValue("@id", id); cmd.Parameters.AddWithValue("@inhalt", inhalt);
cmd.ExecuteNonQuery(); } } } |
EDIT:
Habe nun gesehen, dass ich in den Settern der Properties immer auf != null geprüft habe .
Dies habe ich nun auf != datenbank etc. geändert.
Ändert aber nichts an dem Problem, dass die Veränderung des Wertes nur bis zum ersten Speichern klappt.
Ralf Jansen - Mo 31.03.14 21:12
Zitat: |
C#-Quelltext 1: 2: 3: 4:
| private void options_ModifiedChanged(object sender, EventArgs e) { btnVerbTest.Enabled = false; } | |
Vergleich das mal mit dem Code von mir. Habe ich auch Enabled immer auf false gesetzt? Ich verrate mal gerade nicht warum das falsch ist. Das wäre eigentlich einfach zu sehen wenn du den Code debuggst. Wenn du noch nicht so fit in debuggen von Code bist wäre das gerade genau die Gelegenheit das jetzt auszuprobieren. Setzt einen BreakPoint in den ModifiedChanged EventHandler und schau dir an wann der immer aufgerufen wird und leite davon ab auf was du Enabled wann setzten solltest.
Talemantros - Di 01.04.14 07:28
Guten Morgen,
anfangs hatte ich es wie du mit
C#-Quelltext
1:
| btnVerbTest.Enabled = options.Modified; |
Leider hatte dies nicht den gewünschten Erfolg.
Da ich davon ausging, dass "modified" true, oder false enthält und du in deinem Beispiel davon ausgegangen bist der Speicherbutton sollte auf Tue gesetzt werden, habe ich um den Verbindungsbutton auzugrauen es wie folge geändert:
C#-Quelltext
1:
| btnVerbTest.Enabled = !options.Modified; |
Auch hier geht es nur einmal.
Dann hatte ich in der Klasse modified mit true initialisiert und die Properties auf false setzen lassen bevor das Event ausgelöst wurde um in options.modified = false stehen zu haben.
Nachdem dies alles nicht funktioniert hatte und ich nicht gleich ins Forum schreiben wollte, habe ich dann mal pauschal beim Auslösen des Events auf false gesetzt.
Aber wie beschrieben funktioniert dies auch immer nur einmal.
Jetzt habe ich den Breakpoint wie du es empfohlen hast in den Eventhandler gesetzt und das ganze debuggt.
Beim ersten Ändern einer der Zeilen springt er in das Event und löst es aus. Bei jeder weiteren Änderungen nicht.
Leider weiß ich nicht wie ich es debuggen soll, wenn er nicht mal dahin springt wo ich den Breakpoint gesetzt habe.
Was kann ich tun, wenn das Event anscheinend gar nicht richtig ausgelöst wird?
Danke
Edit:
Was ich mich gerade noch frage. Habe mir den Code noch mal angeschaut. Nach instanzieren der Klasse wäre modified = false.
Nach dem ersten Aufruf würde der Setter feststellen, dass modified false war und jetzt true ist, weil zum Beispiel Password verändert wurde.
Beim nächsten Ändern hat modified ja noch true und der Setter würde feststellen, dass modified true ist und ihn ja nicht ändern, daher auch nicht die Methoden des Setters aufrufen oder? Vielleicht lieg ich jetzt auch ganz quer?!
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| set { if (value != modified) { modified = value; OnModifiedChanged(); NotifyPropertyChanged(); } } |
Gruß
Th69 - Di 01.04.14 10:08
Hallo,
in der btnSpeichern_Click-Methode setzt du direkt btnVerbTest.Enabled = true; - überlege mal was das für die Logik bedeutet?
Du mußt schon ganzheitlich den DataBinding-Ansatz verwenden...
Talemantros - Di 01.04.14 14:12
Hi,
also das war jetzt echt eine schwere Geburt. Ich danke euch vielmals für die Hilfe.
Ich habe durch dieses eine Thema unheimlich viel an Informationen sammeln können.
Und natürlich hoffe ich das ich es jetzt richtig gemacht habe :-)
Habe
C#-Quelltext
1:
| btnVerbTest.Enabled = True |
in
C#-Quelltext
1:
| options.Modified = false |
geändert.
Da es jetzt gut in das Thema rein passt mache ich mal kein neuen Eintrag auf. Hoffe das ist ok
Nachdem ich nun eine Modellklasse, Properties habe, würde mich interessieren, ob man in diesem Zuge den Speichern Button so lässt, oder ob der wie das Load auch völlig anders aussehen sollte
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| private void btnSpeichern_Click(object sender, EventArgs e) { try { localDbQuery.setOptions(dbInfo.getConnStr(), 2, txtBenutzer.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 3, txtPassword.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 4, txtDatenbank.Text); localDbQuery.setOptions(dbInfo.getConnStr(), 5, txtServer.Text);
msgAusgabe.showInformation("Die Daten wurden erfolgreich gespeichert"); } catch (Exception ex) { msgAusgabe.showError(ex.Message); throw; }
options.Modified = false; } |
Hinter dem localDbQuery.setOptions steht
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| public static void setOptions(string connStr, int id, string inhalt) { using (SqlConnection conn = new SqlConnection(connStr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("UPDATE tblOptionen SET inhalt=@inhalt WHERE Id=@id", conn)) { cmd.Parameters.AddWithValue("@id", id); cmd.Parameters.AddWithValue("@inhalt", inhalt);
cmd.ExecuteNonQuery(); } } } |
Danach hoffe ich kann man ein paar Tage was basteln ohne euer Fachwissen.
Aber nochmal vielen Dank. Das hat vieles grundsätzliches für mich geklärt und ich wusste nach welchen Tutorials und Dokus ich gucken muss.
P.S.: Die Databinding habe ich mittlerweile auch über den Designer gelöst.
Gruß
Ralf Jansen - Di 01.04.14 16:14
Man würde beim speichern ähnlich vorgehen wie wir das schon beim Laden der Optionen gemacht haben (getConnectionOptions). Databinding funktioniert ja Bidirectional also stehen die Änderungen aus deiner UI schon in der options Instanz drin. Also äquivalent eine setConnectionOptions Methode schreiben der du die option Instanz übergibst und die wegschreiben läßt. Die Methode gehört dann genauso wie getConnectionOptions nicht zur Form Klasse sondern solltest du in eine eigene Klasse auslagern. localDbQuery und mysqlDbQuery hören sich auch sehr DB nah an und gehören nicht auf die Form.
Die Form sollte vermutlich wenn du es richtig machst nichts mehr von localDbQuery und mysqlDbQuery Wissen sondern nur die Klasse kennen die get/setConnectionOptions hält. Diese Klasse wiederum darf dann localDbQuery und mysqlDbQuery kennen und benutzen. So bist du dann auch schon halbwegs auf dem Weg zu einem schichtenartig aufgebauten System getrennt nach UI-Logik-Persistenz.
Talemantros - Mi 02.04.14 19:40
Hallo,
leider hatte ich gestern beruflich keine Zeit Dinge zu machen, die mir Spaß machen.
Habe heute aber mal versucht die letzten Anregungen umzusetzen.
Folgendes ist dabei raus gekommen:
Code der Form (komplett ohne DBZugriffe)
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65:
| using System; using System.Windows.Forms; using regPosition; using msgbox; using rEcycle.Logics;
namespace rEcycle { public partial class optionenSystem : Form { private connectionOptions options;
public optionenSystem() { InitializeComponent(); }
private void optionenSystem_Load(object sender, EventArgs e) { if (options != null) options.ModifiedChanged -= options_ModifiedChanged; options = connectionOptions.getConnectionOptions(); options.ModifiedChanged += options_ModifiedChanged;
connectionOptionsBindingSource.DataSource = options; }
private void options_ModifiedChanged(object sender, EventArgs e) { btnVerbTest.Enabled = !options.Modified; }
private void btnVerbTest_Click(object sender, EventArgs e) { connectionOptions.mySqlConnTest(); }
private void btnSpeichern_Click(object sender, EventArgs e) { try { connectionOptions.setConnectionOptions(2, options.Benutzer); connectionOptions.setConnectionOptions(3, options.Password); connectionOptions.setConnectionOptions(4, options.Datenbank); connectionOptions.setConnectionOptions(5, options.Server);
msgAusgabe.showInformation("Die Daten wurden erfolgreich gespeichert"); } catch (Exception ex) { msgAusgabe.showError(ex.Message); throw; }
options.Modified = false; } private void btnBeenden_Click(object sender, EventArgs e) { Close(); } } } |
Code der neuen Klasse
C#-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176:
| using localDbConnect; using mysqlDbConnect; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks;
namespace rEcycle.Logics { public class connectionOptions : INotifyPropertyChanged { #region Felder private bool modified; private string server = string.Empty; private string benutzer = string.Empty; private string password = string.Empty; private string datenbank = string.Empty; public event EventHandler ModifiedChanged; public event PropertyChangedEventHandler PropertyChanged; #endregion
public connectionOptions(string server, string benutzer, string password, string datenbank) { this.server = server; this.benutzer = benutzer; this.password = password; this.datenbank = datenbank; this.modified = false; }
#region Events private void OnModifiedChanged() { var changed = ModifiedChanged; if (changed != null) { changed(this, EventArgs.Empty); } }
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { var changed = PropertyChanged; if (changed != null) { changed(this, new PropertyChangedEventArgs(propertyName)); } } #endregion
#region Properties
public bool Modified { get { return modified; } set { if (value != modified) { modified = value; OnModifiedChanged(); NotifyPropertyChanged(); } } }
public string Server { get { return server; } set { if (value != server) { server = value; Modified = true; NotifyPropertyChanged(); } } }
public string Benutzer { get { return benutzer; } set { if (value != benutzer) { benutzer = value; Modified = true; NotifyPropertyChanged(); } } }
public string Password { get { return password; } set { if (value != password) { password = value; Modified = true; NotifyPropertyChanged(); } } }
public string Datenbank { get { return datenbank; } set { if (value != datenbank) { datenbank = value; Modified = true; NotifyPropertyChanged(); } } }
#endregion
#region Methoden public static connectionOptions getConnectionOptions() { string connStr = dbInfo.getConnStr(); return new connectionOptions(localDbQuery.getOptions(connStr, 5, 2), localDbQuery.getOptions(connStr, 2, 2), localDbQuery.getOptions(connStr, 3, 2), localDbQuery.getOptions(connStr, 4, 2)); }
public static void mySqlConnTest() { mysqlDbQuery.connectTest(dbInfo.getMysqlConnStr()); }
public static void setConnectionOptions(int id, string wert) { localDbQuery.setOptions(dbInfo.getConnStr(), id, wert); } #endregion } } |
Jetzt würde ich im nächsten Step noch die TextBoxen in ein UserControl auslagern und dann denke ich könnte ich mich dem Rest des Tools widmen.
Solltet ihr noch grobe Fehler in meinem Code finden, dürft ihr euch gern dazu äußern ;-)
Auch wenn ich es schon einige Male gesagt habe, möchte ich mich gern an dieser Stelle noch mal bedanken.
Das alles war sehr ergiebig für mich.
Danke
Viele Grüße
Ralf Jansen - Mi 02.04.14 20:02
a.)
Zitat: |
C#-Quelltext 1:
| connectionOptions.setConnectionOptions(2, options.Benutzer); | |
Das vier mal aufzurufen ist natürlich immer noch unschön. Die ~magischen~ Nummern 2,3,4,5 müsste so auch plötzlich jeder Nutzer kennen.
Das Detail gehört versteckt in die Methode. Also besser nur options als Parameter an die setConnectionOption Methode und die 4 notwendigen Einzelaufrufe dann intern abhandeln.
b.)
Die Daten und die Methoden Richtung Datenbank sollten nicht in der selben Klasse stecken. Stell dir zum Beispiel vor zwischen dem Teil mit dem Datenbankzugriff und dem Teil mit der UI wäre noch eine Netzwerkverbindung. Also das die beiden Teile in verschiedenen Prozessen stattfinden. Die connectionOptions Klasse kann,wenn sie nur aus Daten besteht, einfach serialisiert werden und zwischen den Prozessen bewegt werden. Ein simpler Client/Server Aufbau also einfach möglich. Der Datenbank Zugriff ist aber nicht transportierbar. Der andere Prozess/Rechner muss ja keinen Zugriff auf den DatenbankServer haben. Du solltest hier also mindestens 3 unterscheidbare Klassen haben. Die Form, die Klasse mit den Daten und der Klasse die den Zugriff Richtung Datenbank behandelt. dbInfo und localDbQuery betrachte ich dann mal als Interna der Datenbank Klasse die kein anderer kennen muss.
Talemantros - Do 03.04.14 10:02
Guten Morgen,
Th69 hat folgendes geschrieben : |
Hallo,
das sieht doch jetzt schon richtig gut aus. :zustimm:
|
wenn man bedenkt wie lange ich jetzt an dem einen Form sitze bin ich auch echt froh, dass es langsam wird :-)
Ich habe mir nun den C# Coding Styleguide angeschaut und alles innerhalb meines Tools umbenannt was umzubennen wäre.
Danke für den Quide.
Weiterhin habe ich den Aufruf auf anraten von Ralf umgebaut
C#-Quelltext
1:
| ConnectionOptionsMethods.SetConnectionOptions(options); |
Und die Methoden aus der Datenklasse entfernt und eine neue Klasse ConnectionOptionsMethod erzeugt.
Soweit so gut.
Nun sitze ich seit gestern Abend an dem UserControl und habe zum Test eins erstellt in das ich alle 4 TextBoxen und den Verbidung Testen und Speichern Button integriert habe. Muss da aber noch dran basteln, da zum einen die Optik nicht stimmt. Obwohl die Buttons im UserControl richtig angezeigt werden, werden Sie in der Form nur teilweise dargstellt.
Und wenn ich die Buttons aus der UserControl raus lasse und nur die TextBoxen integriere, kennen diese die Buttons auf der Form nicht mehr :-)
Muss ich mich jetzt mal dran versuchen.
Gruß
EDIT:
Hi, ich schon wieder :-(
Also, dass mit dem UserControl gehe ich glaube auch falsch an und bei Youtube etc. finde ich nur welche die irgendwie immer anderes damit umsetzen, wie ich das vorhabe oder in dem Personmanagement Projekt drin sind.
Ich habe nun ein UserControl gebaut, welches die 4 TextBoxen enthält und die 2 Buttons (Verbindung testen, Speichern)
Optik siehe Bild! Wenn ich dies nun so laufen lasse funktioniert es auch, aber der Designer meckert den in der Anlage gezeigten Fehler an, den ich nicht zuordnen kann.
Wenn ich die Buttons aus dem UserControl entferne ist der Fehler weg.
Allerdings müsse ich dann in meiner Form, die Buttons haben, die ich aber nicht von der UserControl aus angesprochen bekomme :-(
Im Moment ist in der UserControl alles drin:
1: Databinding
2: Das abonnieren des Ereignisses
3: Und der Code zu der Klasse für Test und Speichern.
Vielen Dank
Gruß
Ralf Jansen - Do 03.04.14 13:38
Aus dem Fehler kann man nur ersehen das du Code ausführst im Umfeld des UserControls (im Konstruktor, Load bzw. Shown Event etc.) den man nur einmal ausführen darf und auch vom WinformsEditor ausgeführt wird. Klick doch mal auf einen der "Aufrufliste anzeigen" Links in der Fehleranzeige. Im angezeigten Stacktrace sollten Methoden von dir auftauchen die die Auslöser des Problems sind. Das sollte dir genügend Information liefern wo das Problem liegt udn warum es knallt. Voraussichtlich ist es Code der eigentlich nicht vom Editor ausgeführt werden sollte.
Zitat: |
Allerdings müsse ich dann in meiner Form, die Buttons haben, die ich aber nicht von der UserControl aus angesprochen bekomme :-( |
Bis wir diese Form des Denkfehlers aus dir raus haben liegen wohl noch ein paar Runden mit Fragen vor uns ;)
Wenn das hier gerade helfen würde, was ich nicht glaube, die Buttons vom UserControl zu trennen gibt es keinen Grund das das UserControl auf die Buttons zugreift. Warum auch. Dann hättest du einen UserControl mit den 4 TextBoxen das die connectionOptions Klasse ändern kann. Also würdest du dem UserControl eine API verpassen um diese Klasse mit dem UserControl auszutauschen.
Dafür wäre eine Property mit getter und setter geeignet. Der Setter würde die Klasse an die Textboxen binden und der getter einfach die reingeschobene und gebundene Instanz wieder zurückliefern.
Die Buttons würden also in ihrem ClickEvent nur das UserControl befragen und sich die connectionOptions Klasse holen und dann das tun was sie halt tun. Die einzige Beziehung die es da gibt ist also das der Code der Buttons das UserControl kennen. Sonst kennt keiner irgendwas;)
Das haben wir im großen vorher übrigens auch so gemacht. Sicherstellen das alle Beteiligten möglichst wenig über den jeweiligen anderen wissen. Die UI kennt nur die Oberflächen der Datenklassen und der Datenbanklogik. Die DatenbankLogik kennt nur die Datenklassen. Und die Datenklasse kennt niemanden.
Wichtig ist hier das Wort Oberfläche der Klasse. Hier im kleinen sollte auch nur den anderen Beteiligten die Oberfläche der jeweilig andern Klassn bekannt sein. Wenn du von irgendwo direkt auf die Textboxen im UserControl zugreifst (oder auf die Buttons) dann läuft da was falsch. Auf die innereinen des UserControls darf nur das UserControl zugreifen. Andere nur über die von dir bereitgestellte Programmieroberfläche wie zum Beispiel die von mir angedeutete Property für die connectionOptions Klasse. Alles andere ist privat.
Talemantros - Fr 04.04.14 12:13
Hi zusammen,
also ich habe mir eben den Fehler noch mal genauer anschauen können.
Da ich keine Methoden oder ähnliches gefunden habe, die ich nicht aufrufen hätte dürfen oder so habe ich mal ein wenig Tante Google bemüht.
Diese hat mir dann mitgeteilt, dass es wohl Fehler im connectionString gibt.
In meinem Stand "AttachDBFilename=|DataDirectory|optionen.mdf;"
Ich habe diesen nun geändert und habe statt des |DataDirectory| den richtigen Pfad auf meinem Laufwerk angegeben.
Jetzt ist der Fehler weg. Mir ist zwar noch ein wenig unklar warum das jetzt mit dem Bau des UserControls aufgetreten ist, da der ConnString vom Visual Studio da abgelegt wurde und vorher auch funktioniert hat, aber Hauptsache es geht.
Ralf Jansen hat folgendes geschrieben : |
Zitat: | Allerdings müsse ich dann in meiner Form, die Buttons haben, die ich aber nicht von der UserControl aus angesprochen bekomme :-( |
Bis wir diese Form des Denkfehlers aus dir raus haben liegen wohl noch ein paar Runden mit Fragen vor uns ;)
|
Ja das glaube ich auch, versuche aber und gelobe Besserung :-)
Zur meiner Verteidigung würde ich sagen, dass ich Beamter im öffentlichen Dienst bin und hier ist auch alles ein wenig langsamer :-) (Einstellungsvoraussetzung :-))
Ich hoffe das zieht, dass ich mich jetzt mal an diesem Klischee bediene.
(Mein Kollege und ich wollten auch schon mal ein Aquarium ins Büro stellen, aber wir haben entschieden das bringt zuviel Unruhe rein ;-) )
Naja, es funktioniert jetzt soweit so ich mir das vorstelle und ich mache mich die Tage mal dran weiter zu machen.
Bin gespannt, wie lange ich ohne euch auskomme.
Vielen Dank und sonniges Wochenende
Gruß
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!