Mich wundert, dass es bisher noch niemand bemerkt zu haben scheint: Der WinForms-Designer (und auch der WPF-Designer) vermischt zwei Aspekte bei der GUI-Programmierung. Das sind die Aspekte Gestaltung und Funktionalität. Dass er das tut, finden wir natürlich alle irgendwie bequem. Doch mich beschleicht das Gefühl, dass hier zuviel des Guten getan wird.
Ein Beispiel ein kleines Formular zum Addieren zweier Zahlen:
Da steckt etwas Gestaltung drin – Steuerelemente wollten ausgewählt und angeordnet werden – und Funktionalität – Ereignisbehandlungsroutinen wollten hinter die Steuerelemente gestellt werden.
Die Programmierung war ganz einfach: Steuerelemente auf das Formular ziehen (Gestaltung), Doppelklick auf ein Steuerelement, um einen Eventhandler zu schreiben (Funktionalität). So weit, so gut. Das möchte ich kaum anders haben.
Wenn ich genau hinschaue, stört mich aber das Ergebnis dieser einfachen Programmierung im Code. Das sieht nämlich so aus:
public partial class WinDoTheMath : Form
{
public WinDoTheMath()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{…}
private void textBox1_Validating(object sender, CancelEventArgs e)
{…}
}
Ganz normal – aber subtil verstörend. Denn was mir hier fehlt, das ist eine Zuordnung der Ereignisbehandlungsroutinen. Die “stehen im Code nur so rum”. Ich muss mir anhand der im Methodennamen steckenden Hinweise zusammenreimen, wie/wann sie zum Einsatz kommen.
Das finde ich inzwischen irgendwie umständlich – oder anders ausgedrückt: Der Zwang zum Zusammenreimen erhöht für mich die Komplexität des Codes. Ich sehe einfach nicht die Abhängigkeiten bzw. Zusammenhänge. Die hat der Designer nämlich im code behind versteckt:
private void InitializeComponent()
{
…
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 12);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(55, 20);
this.textBox1.TabIndex = 0;
this.textBox1.Validating +=
new System.ComponentModel.CancelEventHandler(this.textBox1_Validating);
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(121, 12);
this.textBox2.Name = "textBox2";
this.textBox2.Size = new System.Drawing.Size(55, 20);
this.textBox2.TabIndex = 1;
this.textBox2.Validating +=
new System.ComponentModel.CancelEventHandler(this.textBox1_Validating);
//
// button1
//
this.button1.Location = new System.Drawing.Point(202, 9);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 2;
this.button1.Text = "Add";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
…
Sehen Sie die Zusammenhänge? Die stecken in der jeweils letzten Zeile der Codeblöcke für die Steuerelemente. Dort wird der Eventhandler dem Control zugewiesen. Da geht es um Funktionalität. Und was steht davor? Das geht es um die Gestaltung.
Gestaltung und Funktionalität sind im code behind vermischt. Das ist gut gemeint vom Designer – aber ich finde das nicht mehr verständnisfördernd. Früher war das genial; heute fühle ich mich dadurch verwirrt. Liegt das am Alter? ;-) Nein, ich glaube, das liegt am durch Clean Code Developer geschärften Blick für Verständlichkeit und Abhängigkeiten.
Wo Zusammenhänge nicht sofort klar werden, da regt sich ein ungutes Gefühl bei mir. Und unklar sind sie, wenn ich nicht dort, wo ich Code lese – also im “foreground code” –, mich leicht darüber informieren kann, wer die beteiligten Parteien sind und wie sie zusammen hängen. Weder weiß ich dort, welche Steuerelemente es gibt, noch weiß ich, wie daran Ereignisbehandlungsroutinen geknüpft sind. Das finde ich unschön.
Ich werde daher in Zukunft nicht mehr einfach auf Steuerelementen doppelklicken, um mir Eventhandler generieren zu lassen! Stattdessen schreibe ich die von Hand. Dabei kann ich dann auch wählen, wie ich sie implementiere: ob als eigenständige Methode oder doch nur als Lambda Funktion:
public partial class WinDoTheMath : Form
{
public WinDoTheMath()
{
InitializeComponent();
this.textBox1.Validating += ValidateNumberEntered;
this.textBox2.Validating += ValidateNumberEntered;
this.button1.Click += (s, e) =>
{
var sum = int.Parse(textBox1.Text) + int.Parse(textBox2.Text);
MessageBox.Show(string.Format("Sum: {0}", sum));
};
}
private void ValidateNumberEntered(object sender, CancelEventArgs e)
{
int i;
e.Cancel = !int.TryParse(((TextBox) sender).Text, out i);
this.errorProvider1.SetError((Control)sender,
e.Cancel ? "Input is not an integer!" : "");
}
}
Jetzt habe ich alles auf einen Blick in der Codeansicht: die wirklich relevanten Steuerelemente mit ihrer Funktionalität.
Und ich habe die beiden Aspekte Gestaltung und Funktionalität sauber getrennt. Der Designer ist jetzt wirklich nur noch für die Gestaltung zuständig. Die schaue ich mir im Design View an – und der Code dafür liegt irgendwo verborgen, weil er mich für die Funktionalität, mit der ich sonst beschäftigt bin, nicht interessiert.
Das finde ich sauber. Wer noch?