Laden...

List<int> an ListBox binden und via Button Zahl hinzufügen

Erstellt von GeneVorph vor 5 Jahren Letzter Beitrag vor 5 Jahren 3.116 Views
G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren
List<int> an ListBox binden und via Button Zahl hinzufügen

Hallo,

ich hatte mir als Übung eine vermeintlich ganz einfache Aufgabe gestellt: in einer WPF-app eine ListBox an eine List<int> zu binden und per Button dieser Liste weitere Elemente hinzufügen.

Hier mein XAML-Code für die ListBox:


<ListBox Name="ListBox1" ItemsSource="{Binding myList}" Grid.Column="1">
            
</ListBox>

...und hier der Code für die Liste:


public class TestList
{
        public List<int> myList { get; set; }
         
        public TestList()
        {
            myList = new List<int>();
            
            FillList();
        }

        private void FillList()
        {
            for (int i = 1; i <= 10; i++)
            {
                myList.Add(i);
            }

        }

}

Beim Initialisieren von TestList werden der Liste automatisch die Zahlen 1 - 10 beigefügt.

In MainWindows.xaml.cs habe ich dann noch:


 public MainWindow()
        {
            InitializeComponent();

           
            DataContext = new TestList();
            
        }

         private void btn1_Click(object sender, RoutedEventArgs e)
        {
            TestList tl = new TestList();
            tl.myList.Add(5);           
        }

Beim Starten der App wird die ListBox befüllt und die Werte werden angezeigt. Wenn ich den Button klicke passiert natürlich nichts. Diesen Fehler verstehe ich noch:
mit TestList tl = new TestList();
lege ich ja ein neues Objekt an - das kann also logischerweise nicht funktionieren. Dann habe ich einfach im Namespace TestList tl = new TestList(); angelegt und folgende Änderung an meinem Code vorgenommen:
DataContext = tl;

Auch hier klappt das bis zur Anzeige der Einträge. Drücke ich aber jetzt meinen Button, geht die Anwendung in den Haltemodus und ich bekomme die Fehlermeldung:> Fehlermeldung:

"System.InvalidOperationException: "Ein ItemsControl ist nicht konsistent mit seiner Elementquelle.
Weitere Informationen finden Sie in der inneren Ausnahme. Exception: Informationen für Entwickler (Text-Schnellansicht zum Lesen verwenden):
Die Ausnahme wurde ausgelöst, da der Generator für Steuerelement 'System.Windows.Controls.ListBox Items.Count:11' mit dem Namen 'ListBox1' eine Reihe von CollectionChanged-Ereignissen empfangen hat, die nicht mit dem aktuellen Status der Elementsammlung übereinstimmen. Die folgenden Unterschiede wurden festgestellt:
Gesammelte Anzahl 10 unterscheidet sich von der tatsächlichen Anzahl 11."

Wenn ich die Fehlermeldung richtig verstehe, beschwert sich der Compiler, dass sich plötzlich in meiner Sammlung 11 statt den vorigen 10 Elemente befinden.

Aber dieses Verhalten will ich ja: ich möchte ja meiner Liste ein Element zufügen und dieses Verhalten in der ListBox sehen.
Bei folgendem könntet ihr mir helfen:

  • Wo genau liegt mein Fehler?
  • Welche Grundlage fehlt / habe ich übersehen? --> es ist schwer nach etwas zu suchen, von dem man nicht weiß, wie es heißt...

Viele Dank,
beste Grüße
Vorph

4.938 Beiträge seit 2008
vor 5 Jahren

Für DataBinding mit WPF solltest du eine ObservableCollection<T> (statt List<T>) benutzen, da diese das CollectionChanged-Ereignis auslöst (welches dann von den WPF-Komponenten ausgewertet wird), s. z.B. Gewusst wie: Erstellen und Binden an ObservableCollection.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren

Uff - tatsächlich, so einfach kann's sein! Danke Th69!

Damit komme ich zu dem Teil des Problems, den ich eigentlich bewerkstelligen möchte (und besser vlt. vorher schon in die Frage gepackt hätte):

  • wie gesagt, es handelt sich um eine Übung: zuerst wollte ich mit der Liste experimentieren, dann mit einem HashSet. Letzteres ist für mich besonders interessant, da ich meiner Aufzählung Elemente hinzufügen kann, ohne dass Dopplungen entstehen (z. B. 20 Zahlen von 1 - 10 und im HashSet sind dennoch nur 10 Zahlen).

Beim Stöbern im Forum habe ich einen Thread von 2012 entdeckt, der ziemlich genau mein Problem abdeckt, und zwar hier. Der fünfte Eintrag, der Post von gfoidl, scheint genau das zu sein, was ich suche, allerdings verstehe ich einige Termini nicht.
Was bedeutet Lin-Query?
Hat sich da was getan seit 2012 (mittlerweile haben wir ja C# 7...)

gfoidl schrieb

Für die doppelten Einträge kannst du auch ein HashSet<T> zum Prüfen verwenden und dann der ObservableCollection hinzufügen.

Ich denke, das ist für mich zugänglicher. Ich frage mich aber gerade, wie ich da das Binding bewerkstelligen soll, wenn ich eine ObservableCollection<HashSet<int>> myUniqueCollection anlege und dieser zwar ein HashSet mit den gewünschten Werten übergebe, aber in xaml (z.B. in der ListBox) unter ItemsSource="{Binding myUniqueCollection}" angegeben habe. Dann wird logischerweise in der ListBox nur der Name des HashSets angezeigt, nicht jedoch die einzelnen Items. Stehe ich wirklich so aufm Schlauch?

Gruß
Vorph

4.938 Beiträge seit 2008
vor 5 Jahren

gfoidl meinte Linq-Query.

Außerdem nicht eine ObservableCollection<HashSet<int>> erstellen, sondern zwei Listen pflegen und diese dann verwenden:


HashSet<int> hashSet = new HashSet<int>();
// fill hashSet

ObservableCollection<int> collection = new ObservableCollection<int>(hashSet);

// oder statt dem HashSet eine beliebige Linq-Query:
var query =  from x in X select x.Value order by x.Key;
ObservableCollection<int> collection = new ObservableCollection<int>(query);

(und dann entsprechend die ObservableCollection<> als Eigenschaft des ViewModels und daran binden)

PS: Um doppelte Einträge auszuschließen bei einer Linq-Query gibt es die Distinct()-Methode, s.a. LINQ Distinct Operator.

187 Beiträge seit 2009
vor 5 Jahren

Ich vermute, Du willst doppelte Einträge in Deiner Liste vermeiden.
Hier ein Weg, wie das klappen könnte.
In der View:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <ListBox Name="MyListBox" Grid.Row="0" ItemsSource="{Binding MyList}" />
        <StackPanel Name="InputStackpanel" Grid.Row="1" Orientation="Horizontal">
            <TextBox Name="InputTextBox" Width="100" Text="{Binding Input}" />
            <Button Name="AddToListButton" Content="Add" Click="AddToListButton_Click" />
        </StackPanel>
    </Grid>

Die Code behind:

    public partial class MainWindow : Window
    {
        //private HashSet<int> numbers;

        public ObservableCollection<int> MyList { get; set; }
        public string Input { get; set; }


        public MainWindow()
        {
            MyList = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            //numbers = new HashSet<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            DataContext = this;
            InitializeComponent();
        }

        private void AddToListButton_Click(object sender, RoutedEventArgs e)
        {
            if(int.TryParse(Input, out int newNumber))
            {
                //if (numbers.Contains(newNumber))
                if (MyList.Contains(newNumber)) // auskommentieren, wenn Zeile drüber aktiv ist
                {
                    MessageBox.Show("Die Zahl ist schon vorhanden!", "Fehler", MessageBoxButton.OK, MessageBoxImage.Exclamation);
                }
                else
                {
                    //numbers.Add(newNumber);
                    MyList.Add(newNumber);
                }
            }
            else
            {
                MessageBox.Show("Gib eine Zahl ein!", "Fehler", MessageBoxButton.OK, MessageBoxImage.Exclamation);
            }
        }
    }

Wenn Du alles was mit numbers zu tun hat einkommentierst, dann ist das der Weg mit dem HashSet. Ist aber nicht notwendig!

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren

Wow, vielen Dank für eure Antworten! 🙂

@Th69:

Außerdem nicht eine ObservableCollection<HashSet<int>> erstellen, sondern zwei Listen pflegen und diese dann verwenden:

Ich hatte tatsächlich später noch genau so eine Lösung versucht, hatte aber statt des querys (war mir bis zu deinem Post unbekannt) umständlich über eine if/else-Schleife sortiert - so ist's natürlich übersichtlicher.
Nur mal als Frage zwischendurch: ist dieses Arbeiten/Einpflegen mit und von zwei Listen "legitim" (im Sinne von: Codesmell) oder sollte man (wenn man vor solchen Fragestellungen steht) generell noch mal seine Models überprüfen, ob man da alles richtig gemacht hat? Würde man in der Praxis auf sowas zurückgreifen?

@Caveman:

Ich vermute, Du willst doppelte Einträge in Deiner Liste vermeiden.

Absolut! Nur so kam ich erst auf das HashSet. Ich habe übrigens auf StackOverflow auch ein paar ganz nette Ansätze gefunden zum Thema "ObservableCollection with unique values", die auf Extensions von ObservableCollection beruhen. Allerdings ist das für meinen Fall derzeit eher Overkill: ich will erst mal die Grundzüge verstehen und elegante, einfache Wege gehen. Für eine einzige Collection im Code würde ich da eher nicht auf eine Extension-Method zurückgreifen. Dann Stück für Stück den Schwierigkeitsgrad anziehen. Wie Eingangs erwähnt: es ist ja auch zum Üben und Lernen.

Vielen Dank übrigens, für deine Gegenüberstellung HashSet-Code / ObservableCollection-Ansatz.

Thanks again - wieder was gelernt 🙂

viele Grüße
Vorph

5.299 Beiträge seit 2008
vor 5 Jahren

Ähm - eigentlich - zumindest nach Ansicht einer recht grossen Community - sollte man Wpf so nicht programmieren.
Genuiner Bestandteil von Wpf ist der MVVM-Pattern, also statt des CodeBehinds sollte Databinding verwendet werden.
Wennde MVVM noch nie gehört hast, google mal danach.
Zusätzlich dazu gugge vlt. Wpf-AnwendungsStruktur
Ist zwar vb, aber strukturell gibts da kein Unterschied.

Der frühe Apfel fängt den Wurm.

G
GeneVorph Themenstarter:in
180 Beiträge seit 2015
vor 5 Jahren

Ähm - eigentlich - zumindest nach Ansicht einer recht grossen Community - sollte man Wpf so nicht programmieren.
Genuiner Bestandteil von Wpf ist der MVVM-Pattern, also statt des CodeBehinds sollte Databinding verwendet werden.
Wennde MVVM noch nie gehört hast, google mal danach.

Ja, das ist mir bewusst. Tatsächlich möchte ich mich Richtung MVVM entwickeln. Allerdings ist die Lernkurve IMHO recht hart; man sieht, ich bin ja schon hier, bei einem recht einfachen Untefangen, hängengeblieben.

Tatsächlich ist das Problem meines Erachtens, dass obwohl "Model" noch recht eingängig und leicht erklärbar ist, ebenso wie "View", es bei "ViewModel" (spätestens) schwierig wird: neben vielen Blogs, die ich zum Thema lese, ebenso wie die kryptische msnd-Dokumentation, habe ich am meisten von Video-Tutorials profitiert (einige findet man auf YT): IamTimCorey, Mosh Hamedami und AngelSix. In Bezug auf MVVM heißt es be MH "ViewModel is not associated with what we commonly know as code behind..", bei TC heißt es "basically in your ViewModel resides all that code that you would normally label as your business logic" (er benutzt z. B. caliburn micro und teilt ganz strikt in M - V-VM), während bei AngelSix das Ganze noch kleinteiliger seziert wird. Und als Anfänger fragt man sich dann recht schnell: ja, was denn nu?

Daher wollte ich erst mal Wege gehen, deren Pfaden ich folgen kann 😉 Ich hoffe, in zwei Jahren weiß ich mehr zu MVVM 😃

16.827 Beiträge seit 2008
vor 5 Jahren

Du wirst diesen Pfad aber nicht folgen können, denn WPF ist so konzipiert, dass MVVM verwendet wird.
Mit der Programmierweise eines Windows Forms, in dem man auch schon DataBinding nutzen konnte; viele dies aber aufgrund der Faulheit nicht gemacht haben, wirst Du in WPF nicht weit kommen - und Du wirst von Problem zu Problem un Workaround zu Workaround hangeln, statt Dich um die Funktionen der Anwendung zu kümmern.

Auch wirst Dich damit abfinden müssen, dass es nicht den einen Weg zu MVVM gibt.
MVVM ist ein Pattern für Daten und UI; eine Anwendung besteht aber i.d.R. aus mehr aus nur einer Pattern-Umsetzung; daher sehen Beispiele halt auch unterschiedlich aus.