Laden...

comboBox_SelectedValueChanged wird 3x bei tabControl_SelectedIndexChanged aufgerufen

Erstellt von Garzec vor 5 Jahren Letzter Beitrag vor 5 Jahren 1.561 Views
G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 5 Jahren
comboBox_SelectedValueChanged wird 3x bei tabControl_SelectedIndexChanged aufgerufen

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?

4.939 Beiträge seit 2008
vor 5 Jahren

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();

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 5 Jahren

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.

4.939 Beiträge seit 2008
vor 5 Jahren

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.

16.834 Beiträge seit 2008
vor 5 Jahren
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.

4.939 Beiträge seit 2008
vor 5 Jahren

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).

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 5 Jahren

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.

4.939 Beiträge seit 2008
vor 5 Jahren

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.

G
Garzec Themenstarter:in
50 Beiträge seit 2016
vor 5 Jahren

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

16.834 Beiträge seit 2008
vor 5 Jahren

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.

4.939 Beiträge seit 2008
vor 5 Jahren

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.

16.834 Beiträge seit 2008
vor 5 Jahren

Na.. dann hab ich mich undeutlich ausgedrückt 😉