C# TreeView aus Background Thread füllen

Mike Lowrey

Commodore
Registriert
Juni 2005
Beiträge
4.978
Hallo,

ich arbeite zur Zeit an einer Anwendung die bis zu 1000 Datensätze mit Sub-Datensätzen in einem (WPF) TreeView verwalten soll.

Bisher war das auch kein Problem, weil ich alles einfach im UI Thread gemacht habe, da das allerdings bei häufigeren Aktualisierungen ziemlich langsam wird, hatte ich mir überlegt das ganze in einen Background Thread auszulagern und die Datensätze nach Abruf(von einem WCF Service) nach und nach zu füllen.

Das man das per Dispatcher macht war mir natürlich auch klar, problematisch ist jedoch und das war mir bisher nicht bewusst, dass man offenbar keine TreeViewItems im Background Thread initialisieren kann.
Deswegen schlägt mein Weg per Dispatcher ebenfalls fehl, da ich die TreeViewitems brauchen um denen wiederum SubItems und Kontextmenüeinträge hinzuzufügen.

Nun stellt sich mir die Frage, ob vielleicht doch eine Möglichkeit existiert um dieses Problem herum zu arbeiten.
 
bewusst, dass man offenbar keine TreeViewItems im Background Thread initialisieren kann.

Das ist so nicht richtig.
Du kannst das deshalb nicht, weil der TreeView selbst in einem anderen Thread(nämlich dem GUI-Thread) erzeugt wurde. Wenn du nun TreeViewItems in einem anderem Thread erzeugst und die dann dem TreeView hinzufügen willst, geht das natürlich nicht, weil der Baum einem anderen Thread erzeugt wurde und Threadübergreifende Operationen "verboten" sind.

Was du machen kannst, ist den Baum auch in dem NebenThread erzeugen und dann die Items reinpacken und danach in die GUI packen.
 
Öhm bist du sicher?

Weil die Exception erhalte ich an dieser Stelle.

Code:
TreeViewItem tVItem = new TreeViewItem();
                    tVItem.Header = item.DisplayName;
                    tVItem.Tag = item;
Völlig losgelöst von dem TreeView der UI.

Hinzugefügt würde es dann wie folgt:
Code:
      trvTVChEpg.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
                            {


                                // Fill the treeview with the channels
                                trvTVChEpg.Items.Add(tVItem);
                            });
Aber dahin kommt er gar nicht, weil er an oben genannter Stelle mit dieser Exception
Beim aufrufenden Thread muss es sich um einen STA-Thread handeln, da dies für viele Komponenten der Benutzeroberfläche erforderlich ist.
abbricht.
 
Hatte ich auch überlegt, dass Problem ist das es nicht bei einem TreeViewitem bleibt, sondern eines erstellt wird und diesem im gleichen Atemzug SubItems und Kontextmenüeinträge hinzugefügt werden müssen.

Beides in den UI Thread per Dispatcheraufruf zu verlegen geht dann wiederum nicht, weil man ein TreeViewitem nicht wenn es im TreeView ist modifizieren kann.
Sprich ich müsste immer eine Kopie des TreeView anlegen, dieses in ein IEnumerable konvertieren, das Item modifizieren, dass TreeView leeren und die IEnumerable an das TreeView binden.

Das erscheint mir dann auch nicht mehr sonderlich effizient ;)

Hier mal den kompletten Code um es verständlicher zu machen.

Code:
       foreach (WebChannelBase item in _channels)
                {

                    TreeViewItem tVItem = new TreeViewItem();
                    tVItem.Header = item.DisplayName;
                    tVItem.Tag = item;
                    //Add EPG foreach Channel
                    try
                    {

                        List<WebProgram> program = tvWebClient.GetProgramsForChannel(item.IdChannel, DateTime.Now.AddHours(-2), DateTime.Now.AddHours(10));
                        if (program != null)
                        {
                            int j = 0;
                            for (int i = 0; i < program.Count - 1; i++)
                            {

                                if (j >= 4)
                                {
                                    break;
                                }
                                if (DateTime.Now > program[i].StartTime && DateTime.Now < program[i].EndTime)
                                {
                                    j++;
                                    MenuItem cmItem = new MenuItem();
                                    cmItem.Header = "Record Program";
                                    cmItem.Click += new RoutedEventHandler(MenuItemRecordProgram_Click);
                                    ContextMenu cMenu = new System.Windows.Controls.ContextMenu();
                                    cMenu.Items.Add(cmItem);

                                    TreeViewItem childItem = new TreeViewItem();
                                    childItem.ContextMenu = cMenu;
                                    childItem.Tag = program[i];
                                    childItem.Header = "Jetzt: " + program[i].Title;
                                    childItem.Background = new SolidColorBrush(Colors.Beige);
                                    tVItem.Items.Add(childItem);


                                }

                                if (DateTime.Now < program[i].StartTime && program[i].StartTime.Date == DateTime.Today && program[i].StartTime <= DateTime.Now.AddHours(4))
                                {
                                    j++;
                                    MenuItem cmItem = new MenuItem();
                                    cmItem.Header = "Record Program";
                                    cmItem.Click += new RoutedEventHandler(MenuItemRecordProgram_Click);
                                    ContextMenu cMenu = new System.Windows.Controls.ContextMenu();
                                    cMenu.Items.Add(cmItem);

                                    TreeViewItem childItem = new TreeViewItem();
                                    childItem.ContextMenu = cMenu;

                                    childItem.Tag = program[i];
                                    childItem.Header = program[i].StartTime.TimeOfDay + ": " + program[i].Title;

                                    if (_schedules != null)
                                    {
                                        foreach (WebSchedule s in _schedules)
                                        {

                                            if (s.IdSchedule == program[i].IdProgram)
                                            {
                                                if (s.StartTime <= program[i].StartTime && s.EndTime >= program[i].EndTime)
                                                {//program is fully recorded
                                                    childItem.Background = new SolidColorBrush(Colors.Red);
                                                    cmItem.Header = "Stop Record";
                                                    cmItem.Click += new RoutedEventHandler(MenuItemStopRecord_Click);

                                                    cMenu.Items[0] = cmItem;
                                                    break;
                                                }
                                                else if ((s.StartTime >= program[i].StartTime && s.StartTime <= program[i].EndTime) ||
                                                         (s.EndTime >= program[i].StartTime && s.EndTime <= program[i].EndTime))
                                                {//program is partially recorded
                                                    childItem.Background = new SolidColorBrush(Colors.Orange
      );
                                                }
                                            }
                                        }
                                    }


                                    tVItem.Items.Add(childItem);


                                }

                            }



                            trvTVChEpg.Dispatcher.BeginInvoke(DispatcherPriority.Background, (ThreadStart)delegate
                            {


                                // Fill the treeview with the channels
                                trvTVChEpg.Items.Add(tVItem);
                            });
                        }
 
Zuletzt bearbeitet:
Ok eine möglichkeit die noch bestehen bleibt das ganze in einen Backgroundworker auszulagern, weil der kommuniziert nämlich über events mit der GUI was dir vielleicht helfen könnte.
 
Inwiefern denkst, dass dies besser funktioniert?
Meinst du sowas in Richtung:

Neuen Channel abfangen, in dem Event dann alle subitems abfangen und abschließend hinzufügen?

Denn ansonsten müsste der Backgroundworker doch die gleiche Problematik haben (sprich die Unfähigkeit Treeviewitems zu erstellen) oder?
 
Nicht ganz, aufgrund der Tatsache das der Backgroundworker über Events mit der GUI kommuniziert und du diese Events ja in der GUI abbonieren kannst, dürfte das weniger PRobleme machen.
 
Ehrlich gesagt sehe ich noch nicht den Vorteil darin.

Denn wenn ich die Vorteile(Event basiertes System) vom Backgroundworker nutze wird ja doch wieder alles aufwändige im UI Thread bearbeitet, oder sehe ich das falsch?
 
Hm, ich kenne mich jetzt mit TreeView nicht aus.... aber du kannst (wenn die Klasse das unterstützt) direkt Invoke machen!

Zumindest habe ich so eine komplete Berechnung samt Statusanzeige ausgelagert.

Da dein Tree in der GUI erstellt wird, kannst du:
Code:
if(deineTreeView.InvokeRequired)
    this.Invoke(deineDelegate, deineParams);

machen um zwischen den Threads zu kommunizieren!
 
Ja ich habe es ausprobiert.

Die Aussage im msdn die Backgroundworker Events würden im Kontext des UI Threads ausgeführt hatten mir da Hoffnungen gemacht, dass man vielleicht in diesen die nötigen TreeViewItems erstellen kann.

Zwecks Projekt schicke ich dir eine PN, Danke schon mal für deine Hilfe!

@roker002
Das Problem ist halt das es nicht mit einem TreeViewItem getan ist und ich natürlich alles per Invoke machen könnte, dann aber effektiv auch wieder alles im UI Thread gemacht wird.
 
Zurück
Oben