Ich habe ein TabControl Element mit mehreren Tabs. Auf dem zweiten Tab (Index 1) habe ich eine Combobox, die eine Template Auswahl verwaltet. Das Template Objekt selbst hat eine ID und einen Namen.
internal class Template
{
public Template(double id, string name)
{
ID = id;
Name = name;
}
public double ID { get; private set; }
public string Name { get; private set; }
}
Bei dieser Combobox möchte ich den Namen anzeigen, aber die ID auswählen können. Ich liefere hier mal einen fertigen Beispielcode zum ausprobieren.
Meine Form Klasse
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
cmbx = new Cmbx(comboBox1); // Das Combobox Element übergeben
}
private Cmbx cmbx; // Die Klasse, die diese Combobox verwaltet
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
if (tabControl1.SelectedIndex == 1) // Wurde Tab 2 ausgewählt?
{
cmbx.CreateTemplateSelection(myTemplates); // die Templates der Combobox übergeben
}
}
private void comboBox1_SelectedValueChanged(object sender, EventArgs e)
{
// ausgewählte Template ID hier behandeln
MessageBox.Show($"Combobox Change Event - Selected ID {cmbx.SelectedTemplateID}");
}
}
Meine Combobox Classe
class Cmbx
{
public Cmbx(ComboBox cmbx)
{
this.cmbx = cmbx;
}
private ComboBox cmbx;
private Template[] currentTemplates; // Die zuletzt zugewiesenen Templates
public double SelectedTemplateID // Die aktuell ausgewählte ID abfragen
{
get
{
Template templateInfo = cmbx.SelectedValue as Template;
return templateInfo.ID;
}
}
public void CreateTemplateSelection(Template[] templates) // Die Combobox mit Daten füllen
{
if (currentTemplates != null) // Erster Durchlauf?
{
if (currentTemplates.SequenceEqual(templates)) // Sind die neuen Daten anders?
{
return; // Abbrechen, wenn die Daten identisch sind
}
}
cmbx.DataSource = null; // Combobox leeren
cmbx.Items.Clear();
BindingSource bindingSource = new BindingSource();
bindingSource.DataSource = templates;
cmbx.DataSource = bindingSource.DataSource; // Die Daten der Combobox zuweisen
cmbx.DisplayMember = nameof(Template.Name); // Den Template Namen anzeigen
cmbx.ValueMember = nameof(Template.ID); // Die Template ID hinterlegen
bool valid = templates.Length > 0; // Ist wenigstens 1 Datensatz vorhanden?
if (valid && cmbx.SelectedIndex != 0)
cmbx.SelectedIndex = 0; // Den ersten Index auswählen
currentTemplates = templates; // die neuen Templates merken
}
}
Wenn ich das Projekt starte und den zweiten Tab auswähle, wird das comboBox1_SelectedValueChanged 3x gefeuert.
Beim ersten und zweiten Aufruf erhalte ich von cmbx.SelectedTemplateID eine gültige ID.
Beim dritten Durchlauf erhalte ich einen Nullpointer.
Meine Frage ist:
Warum wird das Event 3x ausgelöst?
Und warum erhalte ich bei den ersten beiden Aufrufen gültige IDs und beim dritten Durchlauf einen Nullpointer?
Die NullReferenceException
erhältst du wohl, weil cmbx.SelectedValue
null
ist.
Dies solltest du also in SelectedTemplateID
abfragen.
Du solltest das Binding auch in einer anderen Reihenfolge durchführen:
cmbx.DisplayMember = nameof(Template.Name); // Den Template Namen anzeigen
cmbx.ValueMember = nameof(Template.ID); // Die Template ID hinterlegen
cmbx.DataSource = bindingSource.DataSource; // Die Daten der Combobox zuweisen
Und das Löschen der DataSource
entfernen (welches dann wohl für die Exception verantwortlich ist) - und zwar beide Zeilen:
// cmbx.DataSource = null; // Combobox leeren
// cmbx.Items.Clear();
Danke für deine Antwort. Befolge ich deine Schritte, erhalte ich sofort einen Nullpointer. Das heißt es werden bei CreateTemplateSelection korrekte Template Objekte übergeben, frage ich aber bei SelectedTemplateID die ID des aktuell selektierten Templates ab, so ist cmbx.SelectedValue sofort null.
Ja, darum sollst du ja auch auf null
abfragen:
public double SelectedTemplateID // Die aktuell ausgewählte ID abfragen
{
get
{
Template templateInfo = cmbx.SelectedValue as Template;
return templateInfo != null ? templateInfo.ID : -1; // or some other default value
}
}
PS: Du könntest aber auch [FAQ] Event nur bei Benutzeraktion auslösen, nicht bei programmtechnischer Änderung anwenden.
cmbx.DisplayMember = nameof(Template.Name); // Den Template Namen anzeigen
cmbx.ValueMember = nameof(Template.ID); // Die Template ID hinterlegen
soll der Code wirklich so sein? 🤔
Im Endeffekt macht der Code das hier:
cmbx.DisplayMember = "Name"; // Den Template Namen anzeigen
cmbx.ValueMember = "ID"; // Die Template ID hinterlegen
Das riecht irgendwie fischig.
Der Kommentar passt auch nicht zum Code.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Abt, was kommt dir daran komisch vor?
Aber nochmal genau darauf geschaut, ist es natürlich so, daß in SelectedValue
dann direkt die "ID" drinstehen sollte (ich habe mich mit SelectedItem
vertan gehabt, sonst hätte ich es heute morgen schon angemerkt).
Also bei den ersten beiden Aufrufen hat cmbx.SelectedValue ein valides Objekt.
@Th69 null darf niemals vorkommen. Da ich nur aus einer Liste von Template Objekten wählen kann, muss ich immer ein valides Objekt selektiert haben und kann davon die ID auslesen.
@Abt eigentlich wie oben. Setze ich einen Haltepunkt bei der Property SelectedTemplateID so habe ich bei Aufruf 1 & 2 einen gültigen Wert und bei Aufruf 3 einen Nullpointer.
Aber bevor man versucht, auch bei Aufruf 3 einen gültigen Wert auszulesen ...
Warum wird das Event 3x ausgelöst? Das Beispielprojekt besteht nur aus diesem geposteten Code. Wenn ich über tabControl1_SelectedIndexChanged den Tab 2 auswähle, erwarte ich, dass die Combobox nur 1x gefüllt wird und er dadurch auch nur 1x comboBox1_SelectedValueChanged auslöst.
Jetzt, wo ich mich an @Th69 gehalten habe, erhalte ich ja schon einmal direkt beim ersten Aufruf den Nullpointer. Warum er aber nun sofort einen Nullpointer wirft und vorher erst 2x valide Werte liefert, ist mir ein Rätsel.
Ja, du erhältst die NullReferenceException
eben gerade durch dein DataBinding, da du eben SelectedValue
falsch benutzt.
Vorher hat es die ersten beiden Male funktioniert, weil dann ValueMember
noch nicht gesetzt war und dann SelectedValue
identisch zu SelectedItem
ist.
Alternativ lass einfach die Zuweisung zu ValueMember
weg (da du ja anscheinend nicht die "ID" direkt auslesen möchtest, sondern Template
als Value
).
Edit: Benutze einfach den Debugger (s. [Artikel] Debugger: Wie verwende ich den von Visual Studio?) und schau dir den Wert von SelectedValue
genau an.
Den Debugger habe ich benutzt, sonst hätte ich die anderen Infos ja nicht gewusst.
Aber "Alternativ lass einfach die Zuweisung zu ValueMember weg" hat funktioniert 😃
Danke
Abt, was kommt dir daran komisch vor?
Weil er damit den Namen der Eigenschaft bekommt und nicht den Wert.
Und wenn man den Quellcode bzw. dessen Kommentare so liest komm ich zu dem Entschluss:
Entweder ist der Quellcode falsch oder die Kommentare.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
So funktioniert aber doch DataBinding. Man gibt den Namen der Eigenschaft an, um zu sagen, was angezeigt wird (bzw. intern als Value
genommen werden soll).
Ich dachte, du würdest dich an dem nameof(...)
direkt stören.
Na.. dann hab ich mich undeutlich ausgedrückt 😉
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code