C# Invoke und Thread - ListView wird nicht aktualisiert

BitMan

Erfahrenes Mitglied
Hallo Leute,

ich schreib gerade ein Tool das komplexe Kopiervorgänge aufgrund von spziellen Datei-Eigenschaften vornimmt.
Das Tool soll als Forms und Console laufen. Soweit geht es auch. Natürlich hab ich ein LIstView eingebaut das in der Form dann auch eine Ausgabe für jede kopierte Datei erzeugt, aber selbst mit INVOKE will das keine Updates machen.

Das Kopieren nimmt eine Klasse vor die doCopy heisst und mit einer Methode start() ausgestattet ist.
Code:
public string tAusgabe = "";
        public void start()
        {
            //*** noch nicht fertig!
            if (pIsConsole) Console.WriteLine("Kopiervorgang gestartet!");
            for (int i = 0; i < 10000000; i++)
            {
                if (pIsConsole) Console.WriteLine(i.ToString());
                else
                {
                    ausgabe.Add(i.ToString());
                    tAusgabe = i.ToString();
                    while (tAusgabe != "") { }
                }
            }
            isStopped = true;
        }

Nun soll die GUI sich bei Inhalt in tAusgabe den String holen und diesen danach löschen.

Im Windows Projekt wird die Methode .start vom Objekt doCopy. mit folgendem Code in einem Thread gestartet
Code:
if (!validate_fields())
                return;
            
            String[] ausschluss_liste = new String[listView1.Items.Count];
            int i = 0;
            foreach (ListViewItem lvi in listView1.Items)
            {
                ausschluss_liste[i++] = lvi.Text;
            }

            String[] datei_liste = new String[listView2.Items.Count];
            i = 0;
            foreach (ListViewItem lvi in listView2.Items)
            {
                datei_liste[i++] = lvi.Text;
            }

            doCopy copyProg = new doCopy(false, cbxQuellVerzeichnis.Text, cbxZielVerzeichnis.Text, checkBox1.Checked, checkBox2.Checked, checkBox3.Checked, checkBox4.Checked,
                                        checkBox5.Checked, ausschluss_liste, datei_liste);

            listView3.Items.Clear();

            Thread thread = new Thread(new ThreadStart(copyProg.start));
            thread.IsBackground = true;
            thread.Start();
            while (!copyProg.isStopped)
            {
                if (copyProg.tAusgabe != "")
                {
                    DoUpdate(copyProg.tAusgabe);
                    copyProg.tAusgabe = "";
                }
            }

Soweit so gut. Nun noch die Methode für das Schreiben in meine ListView .doUpdate
Code:
 public delegate void setDoUpdate(string text);
        public void DoUpdate(string text)
        {
            if (listView3.InvokeRequired)
            {
                setDoUpdate d = new setDoUpdate(DoUpdate);
                listView3.Invoke(d, new object[] { text });
                return;
            }
            
            listView3.Items.Add(text);
        }

Läuft alles sauber. Aber bis meine Methode doCopy.start() fertig ist (aktuell nur eine lange Zählschleife) sehe ich nur das der Scrollbalken größer wird aber keine Texte in der Listview. Muss ich etwas anderes Invoken? Oder Liege ich vollkommen falsch?

cu s00n
BitMan
 

Spyke

Premium-User
ich gebs zu der code verwirrt mich gerade etwas, vielleicht hät ich auch einfach kein Bier trinken solln :D

Erstmal so wie du den Invoke aufrufst macht es auf den ersten blick keinen sinn da du da ja schon wieder in deinem GUI Thread bist.
Der Invoke müsste in der start methode erfolgen.

Und dann hättest du wenn ich das richtig sehe 10 Mio. Einträge als Ausgabe.
Das muss alles gezeichnet werden!
Auch da kommt selbst die WinForm nicht hinterher und würde beim zeichnen "keine Rückmeldung" bringen, rechne das besser um auf Prozent.
 

Alexander Schuc

crazy-weasel
Hallo.

Wie Spyke schon gesagt hast, fügt dein Programm wie es derzeit strukturiert ist alle Einträge erst in die Liste ein, wenn die Vorgänge beendet sind.

Zum Delegate noch: Statt den bei jedem Aufruf neu zu erzeuge, könntest ihn als Member anlegen, oder gleich ganz auf ihn verzichten:

C#:
listBox.Invoke((MethodInvoker)delegate()
{
    DoUpdate(text);
});

Du solltest dir übrigens angewöhnen, deine Steuerelemente zu benennen. Sowas hilft ungemein. :)

lg,..
 

BitMan

Erfahrenes Mitglied
ich gebs zu der code verwirrt mich gerade etwas, vielleicht hät ich auch einfach kein Bier trinken solln :D
Sorry für die Verwirrung. Die Schleife ist nur ein Test. Später sollen da die kopierten Dateien stehen.
Wie kann ich in der Methode DoCopy.start mit Invoke arbeiten. Die kennt ja das Form nicht. Oder bin ich da auf dem falschen Dampfer?

Ich hab das Projekt so angelegt das in der programm.cs bei vorhandenen Parametern die Konsole weitergeführt oder neu initiiert wird.
Wird ohne Parameter gestartet wird mit RUN die Form gestartet. Wenn ich aber Parameter habe, dann wird die Form gar nicht erst geladen und somit gibt es auch kein ListView.
 

BitMan

Erfahrenes Mitglied
Und ich weis das meine Objekte noch besser benannt werden müssen. Ist irgendwie heute und gestern gewachsen. Muss da noch aufräumen.
 

Alexander Schuc

crazy-weasel
Du könntest in deiner DoCopy Klasse ein Event anbieten welches von deiner Anwendung abonniert wird.

Das Event kannst sowohl in der GUI Version, als auch bei der Console verwenden.

Hier ein kleines Beispiel:

C#:
using System;
using System.Windows.Forms;
using System.Threading;

namespace InvokeTest
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            DoSomething worker = new DoSomething();

            worker.SomethingHappened += new EventHandler<DoSomethingEventArgs>(worker_SomethingHappened);

            Thread t = new Thread(worker.Do);
            t.IsBackground = true;
            t.Start();
        }

        void worker_SomethingHappened(object sender, DoSomethingEventArgs e)
        {
            addText(e.Message);
        }

        private void addText(string text)
        {
            if (testBox.InvokeRequired)
            {
                testBox.Invoke((MethodInvoker)delegate()
                {
                    addText(text);
                });

                return;
            }
            testBox.Items.Add(text);
        }
    }

    public class DoSomething
    {
        public event EventHandler<DoSomethingEventArgs> SomethingHappened;

        protected void OnSomethingHappened(string message)
        {
            if (SomethingHappened != null)
            {
                SomethingHappened(this, new DoSomethingEventArgs(message));
            }
        }


        public void Do()
        {
            for (int i = 0; i < 20; i++)
            {
                OnSomethingHappened("Thread - " + i);
                Thread.Sleep(1500);
            }
        }
    }

    public class DoSomethingEventArgs : EventArgs
    {
        public string Message { get; set; }

        public DoSomethingEventArgs()
        {

        }

        public DoSomethingEventArgs(string message)
        {
            this.Message = message;
        }
    }
}
 

BitMan

Erfahrenes Mitglied
Du könntest in deiner DoCopy Klasse ein Event anbieten welches von deiner Anwendung abonniert wird.

Klasse Beipiel, danke.. hab zwar noch nicht alles bis ins Detail gecheckt, komme aber weiter.
Läuft. Allerdings reagieren meine Controls nun anders.

Beispiel: ein FolderBrowserDialog macht auf einmal eine TreadException beim debuggen und wenn ich die EXE direkt starte, dann läuft der Dialog, ist aber leer und man kann keine Verzeichnisse wählen.
 

Alexander Schuc

crazy-weasel
Wenn du dein Beispiel noch mit Code belegen könntest, würden wir uns leichter tun. :)

Aber: auch wenn du einen Dialog anzeigst, musst du darauf achten, dass du im UI-Thread bist.
Prüfen kannst das einfach mit der InvokeRequired Eigenschaft von deinem Form, und dann gegebenenfalls
Invoke auf die Form aufrufen.
 

BitMan

Erfahrenes Mitglied
Wenn du dein Beispiel noch mit Code belegen könntest, würden wir uns leichter tun. :)

Aber: auch wenn du einen Dialog anzeigst, musst du darauf achten, dass du im UI-Thread bist.
Prüfen kannst das einfach mit der InvokeRequired Eigenschaft von deinem Form, und dann gegebenenfalls
Invoke auf die Form aufrufen.

hmmm... den Code posten dann müsste ich das ganze Projekt posten da ich ziemlich umgestrickt habe damit Console & Window gleichzeitig gehen, je nach Start-Option eines davon gestartet wird. Aber ich werde das heute abend mal checken obs in anderem Thread läuft, jetzt wo Du es sagst, klingt das einleuchtend, ...
 

BitMan

Erfahrenes Mitglied
Habs dann direkt ausprobiert. Trotz Invoke geht da nix.

Oder hab ich irgendwas übersehen. Muss ich ein Event anders behandeln?
Code:
private delegate void cbxSourcePath_Click_delegate(object sender, EventArgs e);
        private void cbxSourcePath_Click(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                Invoke(new cbxSourcePath_Click_delegate(cbxSourcePath_Click)); 
            }
            else
            {
                ctrFolderBrowserDialog.RootFolder = Environment.SpecialFolder.Desktop;
                ctrFolderBrowserDialog.Description = "Beschreibender text...";
                ctrFolderBrowserDialog.ShowDialog();
            }
        }

natürlich mit Parametern invoken:
Code:
private delegate void cbxSourcePath_Click_delegate(object sender, EventArgs e);
        private void cbxSourcePath_Click(object sender, EventArgs e)
        {
            if (this.InvokeRequired)
            {
                cbxSourcePath_Click_delegate d = new cbxSourcePath_Click_delegate(cbxSourcePath_Click);
                this.Invoke(d, new object[]{sender, e});
            }
            else
            {
                ctrFolderBrowserDialog.RootFolder = Environment.SpecialFolder.Desktop;
                ctrFolderBrowserDialog.Description = "Beschreibender text...";
                ctrFolderBrowserDialog.ShowDialog();
            }
        }
Aber es steigt immer aus, mit der Meldung:
Für den aktuellen Thread muss der STA-Modus (Single Thread Apartment) festgelegt werden, bevor OLE-Aufrufe ausgeführt werden können. Stellen Sie sicher, dass die Hauptfunktion mit STAThreadAttribute gekennzeichnet ist. Diese Ausnahme wird nur ausgelöst, wenn ein Debugger mit dem Prozess verbunden ist.

Aufgerufen wird die Methode beim Click auf eine ComboBox. Allerdings hab ich den Fehler mit Button.Click und Form.Load auch gehabt.
Meine Main hat auch ein [STAThread]. Daran liegt es auf jeden Fall nicht.
 
Zuletzt bearbeitet: