Laden...

FAQ

[FAQ] Variablennamen zur Laufzeit zusammensetzen / Dynamisches Erzeugen von Controls

Letzter Beitrag vor 12 Jahren 2 Posts 59.209 Views
[FAQ] Variablennamen zur Laufzeit zusammensetzen / Dynamisches Erzeugen von Controls

Hallo Community,

die Frage nach variablen oder dynamischen Variablennamen steht meist im Zusammenhang mit einer Frage wie:

Ich habe mehrere Variablen deren Namen einfach durchnummeriert sind (z.B. text1, text2, ... text10).
Wie kann ich diese in einer Schleife ansprechen?
Kann ich die Zahl im Variablennamen einfach durch die Laufvariable der Schleife ersetzen?

Die Fragen sind berechtigt. C# (und .NET) bieten natürlich die Möglichkeit, mehrere gleichartige Daten gemeinsam zu verarbeiten. Variablennamen zur Laufzeit zusammenzusetzen ist jedoch nicht möglich.

Im Anschluss wird zunächst der der allgemeine Fall behandelt. Für Windows Forms und WPF gibt es meistens bessere Lösungen. Auf diese wird weiter unten eingegangen.

Warum man keine Variablennamen zur Laufzeit zusammensetzen kann?

Es ist nicht möglich, weil .NET eine statisch typisierte Programmiersprache ist. Aber es ist auch nicht notwendig, da .NET für die Verarbeitung gleichartiger Daten Listen zur Verfügung stellt.

Was ist eine Liste?

In .NET ist eine Liste genau das, was man auch im echten Leben darunter versteht: nämlich eine Sammlung von Daten. Wesentlich ist: Man kann alle Elemente einer Liste durchgehen und jedes Element in der Liste bearbeiten!

**Was hilft mir das? **(oder: die Lösung)

Um also das Problem zu lösen, müssen wir unsere vielen Variablen in eine Liste aufnehmen, die wir mit C# durchlaufen können.

Die einfachste (und schnellste) aller Listen ist das Array. Anstatt viele einzelne Variablen zu deklarieren, erzeugen wir ein Array und befüllen es mit unseren Werten:


string[] texte = new string[3]; // hier deklarieren wir Platz für 3 Strings,
texte[0] = "Hallo";             // und hier weisen wir den Elementen
texte[1] = "Welt";              // der Reihe nach die einzelnen
texte[2] = "!";                 // Strings zu

Wenn die Werte, mit denen das Array gefüllt werden soll, von Anfang bekannt sind, geht es stattdessen(!) noch einfacher.

string[] texte = new string[] { "Hallo", "Welt", "!");

Anschließend können wir auf die Elemente der Liste in einer Schleife zugreifen.


// Alle Elemente in einer for-Schleife durchlaufen
for (int i = 0; i < texte.Length; i++) // Zähler von 0 bis zum Ende der Liste (=2) hochzählen
{
   Console.WriteLine(texte[i]);       // Zugriff auf den String am Index i
}

// Oder noch einfacher alle Elemente in einer foreach-Schleife durchlaufen
foreach(string text in texte)         // Alle Elemente der Reihe nach an text zuweisen
{
   Console.WriteLine(text);           // Aktuelles Element ausgeben
}

Im Beispiel geben wir einfach nur die drei Texte aus, aber wir könnten auch etwas anderes machen. Bei einem Array von Zahlen (int[] zahlen = new int[3];) könnten wir auch rechnen.

Ein Array ist eine Liste fester Länge! Es hat immer genau so viele Elemente, wie bei der Deklaration angegeben. Wenn wir vorher nicht wissen, wie lang eine Liste wird, können wir System.Collections.Generic.List<T> verwenden.


List<string> texte = new List<string>();  // eine leere Liste von Strings deklarieren
string eingabe = null;
while(true) 
{
   eingabe = Console.ReadLine();             // Text, den der Benutzer eingibt, einlesen
   if(string.IsNullOrEmpty(eingabe)) break;  // bei leerer Eingabe Schleife verlassen
   texte.Add(eingabe);                       // Eingabe der Liste zufügen
}

Ausgeben können wir die Liste wie beim Array gezeigt, nur dass wir bei der for-Schleife Count statt Length schreiben müssen. Bei der foreach-Schleife gibt es keinen Unterschied.

In besonderen Fällen kann es sinnvoll sein, eine Hashtable (bis .NET 1.1) bzw. ein Dictionary (ab .NET 2.0) oder eine der anderen Auflistungsklassen (Collections) zu verwenden, aber meistens wird Array oder List<T> am besten passen.

Ergänzende Hinweise und weiterführende Informationen
*ArrayList ist völlig veraltet - Da das Framework seit 2005 die typisierte List<T> vorhält, ist es sehr schlechter Stil, weiterhin die untypisierte ArrayList zu verwenden. *Liste, Liste (Datenstruktur) *Übersicht über Auflistungsklassen, Auswählen einer Auflistungsklasse, Häufig verwendete Auflistungsklassen *IEnumerable, Array, List<T>, Dictionary<TKey,TValue> *Schleifentypen (for, foreach, ...) *Referenztypen und das Gegenteil Werttypen *typisierte Programmiersprache

Die Autoren

Dieser Text ist eine Gemeinschaftsproduktion von ErfinderDesRades, herbivore, JuyJuka und zero_x. Darin sind Verbesserungsvorschläge von dN!3L, FZelle, gfoidl, JAck30lena, MarsStein, michlG, ujr eingeflossen. Alle Namen in alphabetischer Reihenfolge.

Gibt es keine besseren Lösungen für Windows Forms und WPF?

Der Designer erzeugt (automatisch) einzelne Variablen, eine für jede TextBox, ComboBox, ..., eben für jedes Steuerelement. Das kann man nicht umgehen, es sei denn, man verzichtet auf den Designer. Natürlich könnte man den Inhalt der einzelnen Variablen - wie oben gezeigt - in ein Array oder eine Liste schreiben, aber zuerst müssen wir uns einige Fragen stellen.

Kommt man mit einem Listen-Control einfacher und besser ans Ziel?

Windows Forms und auch die Windows Presentation Foundation (WPF) haben für die häufigsten Aufgaben, die man mit einem Array von Steuerelementen lösen könnte, eine bessere Lösung mit nur einem Steuerelement. Diese Steuerelemente nennt man Listen-Controls, da sie eben Listen von Daten darstellen und/oder bearbeiten können. Diese Steuerelemente sind für den Programmierer einfacher zu handhaben, für die Benutzer vertrauter und für das System einfacher/performanter darzustellen als eine Menge einzelner Steuerelemente.

Welche Listen-Controls gibt es und wofür eignen sie sich am besten?

Hier folgt eine Auflistung der Windows Forms Listen-Controls, zusammen mit einer kurzen Zusammenfassung was das Steuerelement kann und was der typische Anwendungsfall ist. Für WPF gibt es immer vergleichbare Steuerelemente, oft sogar mit dem gleichen Namen. Man muss sich also nur noch fragen: Welches dieser Steuerelement passt für meinen Anwendungsfall am besten?
*ListBox

Die ListBox kann eine Liste von Werten anzeigen und erlaubt dem Benutzer einen oder mehrere Werte zu markieren (normal: blauer Hintergrund) und damit auszuwählen. Direkt ändern kann der Benutzer die Werte nicht.

Tipp: Mehrspaltig? Gleich zu DataGridView (oder ListView im Details-Modus) gehen.

*ComboBox

Die ComboBox erlaubt wie eine TextBox eine Eingabe über die Tastatur und über eine ausklappbare Liste ähnlich einer ListBox eine Auswahl aus vorhandenen Werten. Der Name erklärt sich aus der Kombination einer TextBox mit einer (ausklappbaren) ListBox.

Üblicherweise wird die ComboBox verwendet, um dem Benutzer zu erlauben ein einzelnes Element auszuwählen, ohne dabei viel Platz auf dem Form zu verbrauchen. Zur reinen Anzeige einer Liste ist das Steuerelement nicht geeignet. Es fällt also etwas aus der Reihe der anderen Listen-Controls heraus.

Tipp: Mit ComboBox.DropDownStyle = DropDownList kann die ComboBox so eingestellt werden, dass der Benutzer nur auswählen kann, aber nichts eingeben.

*CheckedListBox

Die CheckedListBox arbeitet wie eine ListBox, aber mit der Erweiterung um eine CheckBox für jedes Element. Der Benutzer kann also Elemente auswählen wie in einer ListBox, aber auch Elemente mit einem Häkchen markieren/auswählen. Dies hat den Vorteil, dass die Auswahl per Häkchen nicht durch einen unbedachten Klick verloren gehen kann, wie die das bei einer normalen Markierung (blauer Hintergrund) der Fall ist.

Üblicherweise wird die CheckedListBox verwendet, wenn man eine Liste von Optionen hat und/oder der Benutzer eine Mehrfachauswahl treffen soll.

Tipp: Mit CheckedListBox.CheckOnClick = true kann der Benutzer auch auf den Text klicken, um das Häkchen für das Element zu setzen oder zu entfernen.

*ListView

Das ListView kann eine Liste von Elementen in verschiedenen Ansichten darstellen, mit Icons, Gruppen und zusätzlichen Informationen und mehr.

Üblicherweise wird eine Auswahl von Elementen, die detaillierte Informationen haben oder zusätzlich gruppiert werden müssen, hiermit dargestellt, z.B. die Dateien im Windows-Explorer. Das ListView dient überwiegend zur Anzeige von Daten. Ändern kann der Benutzer nur die Informationen in der ersten Spalte und auch das nur dann, wenn der Programmierer das durch ListView.LabelEdit = true erlaubt hat.

Tipp: Die verschiedenen Ansichten werden über ListView.View eingestellt.

Tipp: Bei ListView.View = Details sieht man erst dann etwas, wenn man mindestens eine Spalte in ListView.Columns definiert hat.

*DataGridView

Das DataGridView kann Daten als Liste mit mehreren Spalten darstellen und auch bearbeiten, wobei beim Bearbeiten sogar andere Steuerelemente in die Zelle eingeblendet werden können (z.B. ComboBox).

Üblicherweise wird das DataGridView immer für mehrspaltige Ansichten verwendet. Mit dem DataGridView können alle Arten von tabellarischen Daten angezeigt und bearbeitet werden. Es ist (vom PropertyGrid abgesehen) das einzige Listen-Control, das eine direkte Bearbeitung aller Daten durch den Benutzer ermöglicht.

*TreeView

Das TreeView kann eine hierarchische/baumartige Struktur von Elementen darstellen, deren Unterelemente man aufklappen und einklappen kann.

Üblicherweise werden mit dem TreeView nur hierarchische Darstellungen abgebildet, z.B. die Ordner im Windows-Explorer.

Tipp: Die Elemente im TreeView, die TreeNodes, haben eine Tag-Property, in welcher man die Daten, die zu dem Element gehören, ablegen und so einfach darauf zugreifen kann.

Tipp: Im Event AfterSelect des TreeViews kann man auf die Auswahl eines TreeNode reagieren und z.B. dynamisch weitere TreeNodes hinzufügen (lazy loading).

*PropertyGrid

Das PropertyGrid fällt etwas aus der Reihe. Es stellt nicht eine Liste von (gleichartigen) Daten dar, sondern eine Liste von (unterschiedlichen) Properties eines Objekts.

Üblicherweise kommt das PropertyGrid in Programmen für Endanwender selten zum Einsatz, wobei es sich für das Anzeigen und Ändern von Einstellungen/Optionen und auch in vielen anderen Fällen durchaus gut eignen würde.

Tipp: Die Anzeige des PropertyGrid kann sehr umfassend beeinflusst werden, nicht nur in Bezug auf die angezeigten Namen für die Properties, Kategorien usw., sondern man kann sogar eigene Editoren für bestimmte Arten von Properties definieren, z.B. für eine Farbauswahl.

**
Was wenn keins der Listen-Controls für meinen Anwendungsfall passt?**

Das ist unwahrscheinlich! Insbesondere mit dem DataGridView lässt sich fast alles realisieren, was mit der Anzeige, dem Auswählen und dem Anzeigen von tabellarischen Informationen zu tun hat.

Bevor man daran denkt, ein Form aus einer Menge einzelner, gleichartiger Controls auszubauen, sollte man wissen, dass es unter Windows Forms schon ab wenigen hundert Controls, zu ernsthaften Performance-Problemen und anderen Schwierigkeiten kommen. Man sollte also auf die Vorgehensweise, die oben für den allgemeinen Fall beschrieben wurde, überhaupt nur dann zurückgreifen, wenn die Anzahl der Controls überschaubar ist und bleibt. Ansonsten sollte man auf Listen-Controls zurückgreifen oder erwägen, den Inhalt der Forms selber zu zeichnen, siehe [Tutorial] Zeichnen in Windows-Forms-Programmen.

Wie kann man - alle Warnungen im Sinn - die Designer Elemente in ein Array übertragen?

Da Steuerelemente Referenztypen sind, kann man sie in mehrere Variablen gleichzeitig speichern. Jede Variable referenziert dann dasselbe(!) Steuerelement, d.h. der Designer erzeugt seine Variablen textBox1, textBox2, ... und wir übernehmen anschließend eine Referenz darauf in unsere Liste:


   // Hier wird das Array in nur einer Anweisung initialisiert und befüllt
   // Vorteilhaft ist das vor allem, weil der Compiler die Anzahl der Elemente
   // selbst erkennt, sodass nachträgliches Einfügen/Entfernen keine Gefahr 
   // einer Falsch-Dimensionierung birgt.
   TextBox[] textBoxes = new TextBox[] { this.textBox1, this.textBox2, this.textBox3 };

   string gesammelterText = string.Empty;
   for(int i = 0; i < textBoxes.Length; i++)
   {
      gesammelterText += textBoxes[i].Text;
   }
   MessageBox.Show(gesammelterText);  

Das ist nur ein Beispiel. Man kann noch viel mehr machen, als nur den Text auszulesen.

Ganz wichtig: Die Textboxen kann man erst in die Liste packen, nachdem der Designer sie erzeugt hat, also nach dem Aufruf von InitializeComponent()

Kann man nicht direkt auf die Controls-Collection zugreifen?

Das könnte man zwar, aber mehrere Gründe sprechen dagegen:
*Die Elemente der Controls-Collection habe den (statischen) Typ Control. Um auf spezielle Eigenschaften z.B. einer TextBox zugreifen zu können, muss man erst casten, was nicht nur unschönen und sperrigen Code erfordert, sondern auch immer das Risiko eines InvalidCastException birgt. *In der Controls-Collection sind alle (Arten von) Controls enthalten, die sich direkt auf dem Form befinden und nicht nur die ausgewählten, die wir in dem obigen Beispiel explizit in das Array übertragen haben. Das ist nicht nur störend, wenn von von Anfang an andere Controls vorhanden sind, sondern verschiebt auch die Indizes, wenn später weitere Controls hinzukommen. *Anderseits kann man über die Controls-Collection dann nicht direkt auf alle Steuerelemente zugreifen, wenn diese auf Container-Controls (Panel, GroupBox, SplitContainer, ...) platziert sind, deren Controls-Collection man nun ebenfalls berücksichtigen müsste (Rekursion). *Beim unschönen Zugriff über den Namen der Controls muss man diesen erst als String zusammensetzen. Das ist fehleranfällig, wird erst zur Laufzeit geprüft und ist insgesamt nicht besonders professionell.

Die Controls-Collection ist eher für das Framework gedacht, und man sollte sie besser nicht im eigenen Code benutzen, es sei denn, man erstellt dynamisch zur Laufzeit Steuerelemente. Dann muss man diese natürlich der Controls-Collection hinzufügen, damit sie angezeigt werden.

Wie kann man - weiter alle Warnungen im Sinn - dynamisch Controls erstellen?

In seltenen Fällen kann es sinnvoll sein, Controls dynamisch zu erstellen. Wie das geht, zeigt der folgende Code. Im Grunde wird dabei nur der Code, den Visual Studio für die Erzeugung eines einzelnen Controls generiert, in eine Schleife gepackt. Dabei sollte man aber weiter alle oben genannten Warnungen im Sinn behalten und diese Technik insbesondere nicht als Ersatz für ein Listen-Control verwenden.


   List<Button> buttons = new List<Button> ();

   for(int i = 0; i < 10; i++)
   {
      buttons.Add (new Button ());
      buttons[i].Text = "Aktion " + i;
      buttons[i].Location = new Point (10, 10 + i * buttons[i].Height * 3 / 2)
      buttons[i].Click += AllButtonsClick;
      this.Controls.Add(buttons[i]);
   }

protected void AllButtonsClick (Object sender, EventArgs e)
{
   Button buttonClicked = (Button)sender;
   // ...
}

**
Welche der gezeigten Möglichkeiten sollte man wählen?**

Die erste Wahl sollte immer die Verwendung des passenden Listen-Controls sein.

In Fällen, wo dies nach gründlicher Prüfung nicht in Frage kommt, hängt es ein bisschen von der Art und Anzahl der Controls ab. Die Anzahl sollte wie gesagt, eng begrenzt sein und auf keinen Fall darf sie beliebig wachsen. Wenn man mehr als 100 Controls erstellen will, ist es definitiv der falsche Weg. Ein Array von PictureBoxen zum Anzeigen eines Fotoalbums, das beliebig viele Bilder enthalten kann, ist selbst dann der falsche Weg, wenn die meisten Fotoalben nur einige wenige Bilder enthalten. Maßgeblich ist also immer die Zahl der Controls im Worst Case.

Wenn es um relative wenige Controls der gleichen Art geht (vielleicht 3 bis 5), deren Anzahl zudem fest ist, kann man diese mit dem Designer erzeugen und dann wie oben gezeigt in eine Liste füllen.

Wenn es um eine etwas größere Menge von gleichartigen Controls geht (vielleicht 6 bis 20) oder deren Anzahl variabel, aber begrenzt ist, sollte man diese besser wie gezeigt dynamisch in einer Schleife erzeugen.

Wenn es um noch größere Mengen an Controls ginge, dann sollte man diese weder mit dem Designer noch dynamisch erzeugen, sondern ganz vermeiden, z.B. indem mal alles selber zeichnet.

Die Autoren

Dieser Text ist eine Gemeinschaftsproduktion von herbivore und JuyJuka. Darin sind Verbesserungsvorschläge von FZelle, gfoidl und Th69 eingeflossen. Alle Namen in alphabetischer Reihenfolge.