Form ein- ausblenden über einen weiteren Thread



  • Darf ich mal kurz stören ?

    Ich hänge schon den ganzen Tag an dieser einen Sache und denke ihr könnt mir dabei helfen. Wahrscheinlich ist die Lösung recht simpel. Es gibt eine Form namens BitteWartenBox. Diese Form wird momentan bei Programmstart instanziert - aber nicht angezeigt.

    Mein Ziel ist es, diese Form nach Bedarf ein- und auszublenden. Innerhalb dieser Form befindet sich eine PictureBox mit einem animierten Icon. Angezeigt werden soll die Form bei bspw. Ladevorgängen.

    Mein erster Versuch war wenig spektakulär mittels der Methoden Show u. Hide. Allerdings "laggt" dann das animierte Icon während die Daten geladen werden, und das war so nicht gedacht.

    Also dachte ich, machste das ganze in nem extra Thread:

    System.Threading.Thread waitThread;
    BitteWartenBox IBitteWartenBox;
    
    protected void ThreadWartenBox (Object position)
            {
                try
                {
                    IBitteWartenBox.ShowDialog(); //Show Dialog weil ansonsten sofort geschlossen wird
                    IBitteWartenBox.Location = (System.Drawing.Point)position;
                }
    
                catch (System.Threading.ThreadAbortException)
                {
    
                    //System.Threading.Thread.ResetAbort(); //BitteWarten Flackert
                    //IBitteWartenBox.Visible = true;                
                    //IBitteWartenBox.slowHide();                   
                }
            }
            public void startWartenBox()
            {            
                //SOLL Position wird als Objekt übergeben           
                if (waitThread.IsAlive == false)
                {
                    waitThread.Start(new System.Drawing.Point(this.Location.X, this.Location.Y + menuStrip1.Size.Height * 2));
    
                }
            }
            public void stopWartenBox()
            {
                waitThread.Abort();
            }
            protected void loadWartenBox()
            {           
                //Wenn der Thread noch nicht gestartet ist
                waitThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ThreadWartenBox));
    
                //Debug
                if (waitThread.IsAlive) status.Text = "Warten Thread läuft"; else status.Text = "Warten Thread beendet";
            }
    

    ...was soweit auch genau 1mal klappt. Danach erhalte ich diese Exception:

    Der Thread wird ausgeführt oder wurde abgebrochen. Neustart nicht möglich.

    Der Thread wurde ja bereits abgebrochen. Wie bekomme ich diesen nun wieder ans laufen ?

    Auch über den BackgroundWorker hab ich es versucht. Allerdings bin ich da - wie auch hier - anscheinend gezwungen die BitteWartenForm über ShowDialog aufzurufen, da der Thread ja ansonsten sofort beendet wird.

    Was mache ich falsch ?



  • So einiges, was du falsch machst.

    Lies dir am besten mal folgende drei Tutorials (bzw. FAQs) aus einem anderen Forum durch:
    Multi-Threaded Programmierung
    Warum blockiert mein GUI?
    Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

    Soll denn dein Hauptfenster weiterhin bedienbar bleiben? Wenn ja, dann solltest du auch Show() anstatt ShowDialog() aufrufen.

    Und ich persönlich würde auch den Backgroundworker verwenden:
    - vorher dialog.Show() aufrufen,
    - dann Backgroundworker starten (RunWorkerAsync)
    - im RunWorkerCompleted-Ereignis dann dialog.Hide() aufrufen

    Du mußt aber auf jeden Fall sicherstellen, daß dein (Neben-)Thread nicht 100% Prozessorlast erzeugt, d.h. mindestens ein Thread.Sleep(1) sollte da rein, damit auch der GUI-Thread (d.h. deine Animation) noch ruckelfrei angezeigt wird.

    Und zu deiner letzten Frage (bzw. der Fehlermeldung):
    ein einmal abgebrochener Thread kann nicht wieder neu gestartet werden, sondern du mußt eine neue Instanz erstellen.
    Wenn du viele Threads hast, solltest du evtl. besser den ThreadPool verwenden.

    Ab .NET 4 kannst du auch die Klasse 'Task' dafür verwenden.



  • Moin moin ...

    also ich "tue" mich schon verdammt schwer das zu verstehen.

    Geplant war ja, das nur die BitteWartenBox selber in einem weiteren Thread ein, bzw. ausgeblendet wird, einfach nur um eine flüssige "Animation" des Icons zu erreichen.

    Ich bin mir ja bewusst, dass i.d.R. die "aufwändigen" Methoden für einen weiteren Thread Verwendung finden. Bei mir ist es nur die simple Bitte Warten Box, die nach beenden einer "aufwändigen" Methode ausgeblendet werden sollte. Aus mir einem im Moment noch nicht nachvollziehbaren Grund klappt das nicht.

    Im Moment schauts etwa so aus:

    BitteWarten

    -FormBorderStyle None
    -TopMost True
    -WindowsState Normal

    public BitteWartenBox() 
            {
                InitializeComponent();              
                roundControl(this, 0, 0, this.Size.Width, this.Size.Height, 10);              
            }        
    
            protected void roundControl(Form c, int x, int y, int weite, int höhe, int radius)
    ...
    ...
    

    Hauptprogramm

    namespace neuVanNetten
    {
        public partial class Hauptprogramm : Form
        {
            public Hauptprogramm(BenutzerInfoKlasse info)
            {
                InitializeComponent();
    
                IBenutzerInfo = info;
    
                RefreshSettings();
    
                ISound = new Sound(this);            
    
                //Bitte warten Box laden      
                IBitteWartenBox = new BitteWartenBox();
            }
    ...
    ...
    protected BitteWartenBox IBitteWartenBox;
    ...
    ...
    public void ShowWartenBox()
            {
                IBitteWartenBox.Show();          
            }
    ...
    protected void startFragenEditor()
            {
                //Bei Threadübergreifung
                if (this.InvokeRequired)
                { 
                    this.Invoke(new MethodInvoker(startFragenEditor));
                    return;
                }
    
                ISound.playNewPanel();
                fragenkatalogBearbeitenToolStripMenuItem.Enabled = false;
                IEditorFragen = new EditorFragen(this);
    
                HauptPanel.Controls.Add(IEditorFragen);
    
                //Blendet die BitteWartenBox aus
                LoadWorker1.ReportProgress(100);
            }        
    ...
    ...
    private void LoadWorker1_DoWork(object sender, DoWorkEventArgs e)
            {
                startFragenEditor();
            }
    
            private void LoadWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
            {
                //versteckt die Bitte Warten Box
                IBitteWartenBox.Hide();               
            }
    ...
    ...
    

    Wie ihr sehen könnt, versuche ich nun eine andere Richtung. Eben das "aufwändige" Laden von Fragen - im EditorFragen Konstruktor - mittels BackgroundWorker.

    Wobei das scheinbar nicht den erwünschten Effekt bringt. Die BitteWarten Box wird zwar endlich ein und ausgeblendet - laggt aber weiterhin, was ich auch zu verstehen glaube.

    //Bei Threadübergreifung
                if (this.InvokeRequired)
                { 
                    this.Invoke(new MethodInvoker(startFragenEditor));
                    return;
                }
    

    ... bewirkt ja, so wie ich das verstanden habe, das die Methode nicht vom aufrufenden Thread, sondern vom GUI Thread aufgerufen wird, was ja eigentlich nicht Sinn der Sache ist. Habe ich das soweit richtig verstanden ?



  • Hallo nochmals,

    alle Zugriffe auf GUI-Elemente dürfen nur im GUI-Thread (d.h. Hauptthread) erfolgen, d.h. du benötigst dafür keinen eigenen Thread. Nur aufwendige Berechnungen bzw. Festplattenzugriffe (Laden/Speichern) etc. kann man sinnvoll in einen eigenen Thread auslagern (und gerade im Wechselspiel mit der GUI bietet sich eben dann der Backgroundworker an).

    Dein Code bisher entspricht genau dem Beitrag "Die Falle" aus "Warum blockiert mein GUI?"!!!

    Du darfst also nur das "Daten laden" in einen eigenen Thread (d.h. mittels Backgroundworker.DoWork) auslagern, KEINE GUI-Aktionen!
    Sollte das Öffnen eines Formulars/Dialogs zulange dauern (InitializeComponents), so hast du zuviele GUI-Elemente darauf (und solltest diese reduzieren, z.B. durch Verwendung einer Tabelle oder aber selbstgezeichneten Controls).

    Am besten, du entfernst zuerst einmal wieder den kompletten Thread-Krempel und danach zeigst du mal den Code, welcher ausgelagert werden soll...

    Edit: Was du eigentlich benötigst, ist ein sogenannter "Splash-Screen" - dafür gibt es schon diverse Lösungen, such einfach mal danach...

    P.S. Deine Namensgebung bei deinem Code hat mich zuerst sehr verwirrt, da nach Microsoft-Richtlinien "I..." für Interface-Klassen gedacht sind (und nicht als Member)...



  • Am besten, du entfernst zuerst einmal wieder den kompletten Thread-Krempel

    Darauf hab ich gewartet 🙂 .

    Du darfst also nur das "Daten laden" in einen eigenen Thread (d.h. mittels Backgroundworker.DoWork) auslagern, KEINE GUI-Aktionen!

    Okay ... das habe ich dann jetzt auch mal begriffen.

    Also alles auf Anfang, Krempel im Hauptprogramm ist entfernt.

    ... im Hauptprogramm (zur Erinnerung)

    protected void startFragenEditor()
            {           
                ISound.playNewPanel();
                fragenkatalogBearbeitenToolStripMenuItem.Enabled = false;
                IEditorFragen = new EditorFragen(this);
    
                HauptPanel.Controls.Add(IEditorFragen);
            }
    

    EditorFragen Konstruktor

    public partial class EditorFragen : UserControl
        {
            public EditorFragen(Hauptprogramm instanz)
            {
                InitializeComponent();
    
                IHauptprogramm = instanz;            
    
                //vorrübergehend
                comboBoxFachbereich.SelectedIndex = 0;
                comboBoxSchwierigkeit.SelectedIndex = 0;
                comboBoxTyp.SelectedIndex = 0;
    
                loadQuestionWorker.RunWorkerAsync();
                IHauptprogramm.IBitteWartenBox.Show();
                //loadQuestionCatalog();            
            }
    
    private void loadQuestionsWorker_DoWork(object sender, DoWorkEventArgs e)
            {
                loadQuestionCatalog();
            }
    
            private void loadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
            {
                IHauptprogramm.IBitteWartenBox.Hide();
            }
    

    und die "aufwändige" Methode

    protected   void loadQuestionCatalog()
            {
                //XML Dokument
                XmlDocument XMLdoc = new XmlDocument();
    
                //Hilfs TreeView erstellen
                TreeView helpTreeView = new TreeView();
    
                DiskIO IDiskIO = new DiskIO();
                XMLdoc = IDiskIO.loadXMLFile(new System.IO.FileInfo ("D://Fragenkatalog2.xml"), IHauptprogramm.progressBar);
    
                //prüfen ob beim Laden der Datei was schief gegangen ist
                if (IHauptprogramm.checkXMLdocOK(XMLdoc) == false) return; 
    
                //TreeView mit Daten aus dem XMLdoc füllen
                IHauptprogramm.fillTreeView(helpTreeView, XMLdoc);
    
                //Und die geladenen Daten noch ins DataGridView eintragen
                dataGridView1.Rows.Clear();
    
                //Fragen Stammknoten holen
                TreeNode questionblock = helpTreeView.Nodes[0];
    
                //Hier stehen alle Fragen drin (F0,F1 usw. welche wiederum die Nodes für Frage etc. haben)
                int nr = 0;
    
                foreach (TreeNode node in questionblock.Nodes)
                {
                    //Neue Zeile erstellen
                    dataGridView1.Rows.Add();
    
                    //Infos zur Frage
                    dataGridView1.Rows[nr].Cells["Fachbereich"].Value   = node.Nodes[0].Nodes[0].Text;
                    dataGridView1.Rows[nr].Cells["Schwierigkeit"].Value = node.Nodes[1].Nodes[0].Text;
                    dataGridView1.Rows[nr].Cells["Typ"].Value           = node.Nodes[2].Nodes[0].Text;
                    dataGridView1.Rows[nr].Cells["Frage"].Value         = node.Nodes[3].Nodes[0].Text;                
    
                    //Und die Antworten                
                    foreach (TreeNode n in node.Nodes)
                    {
                        if (n.Text == "Antworten")
                            {
                                //Antworten Stamm gefunden
                                foreach (TreeNode x in n.Nodes) dataGridView1.Rows[nr].Cells["Antworten"].Value = dataGridView1.Rows[nr].Cells["Antworten"].Value + x.Nodes[0].Text + "<!>";
                            }
    
                        if (n.Text == "richtigeAntworten")
                            {
                                //Antworten Stamm gefunden
                                foreach (TreeNode x in n.Nodes) dataGridView1.Rows[nr].Cells["richtigeAntworten"].Value = dataGridView1.Rows[nr].Cells["richtigeAntworten"].Value + x.Nodes[0].Text + "<!>";                            
                            }                    
                    }
    
                    //Feedback 
                    dataGridView1.Rows[nr].Cells["Feedback"].Value = node.Nodes[6].Nodes[0].Text;
    
                    //Wenn keine GUID für die Frage gepspeichert ist ...
                    if (node.Nodes[7] != null) dataGridView1.Rows[nr].Cells["ID"].Value = node.Nodes[7].Nodes[0].Text;
                    else /*Hier eine neue erstellen */ dataGridView1.Rows[nr].Cells["ID"].Value = System.Guid.NewGuid().ToString();
    
                    //nr erweitern
                    nr++;               
                }
    
                //Status aktualisieren
                IHauptprogramm.unsavedData = false;
            }
    

    Bei der Fortschrittsanzeige wird es Probleme geben, beim DataGrid dann auch, das habe ich ja bereits gelernt 🙂 Dann lass es mal ordentlich interessantes regnen.



  • Hi,

    wie ich schon schrieb, darfst du keine GUI-Aktionen in einen Thread auslagern (außer Control.Invoke benutzen).
    Da deine Methode 'loadQuestionCatalog' hauptsächlich das Befüllen des DataGridViews und TreeView ist, müssen diese zwingend im GUI-Thread laufen.

    Du solltest dir vllt. auch Gedanken bzgl. Trennung von Daten und Controls machen (Stichwort: DataBinding).

    Die einzige Möglichkeit, die ich sehe, um zwei Fenster "gleichzeitig" zu aktualisieren, wäre ein Timer, denn zumindestens der Windows.Forms.Timer läuft im GUI-Thread.

    Du zeigst also vor dem Aufruf der 'loadQuestionCatalog'-Methode dein Wartefenster an und startest den Timer (Interval entsprechend setzen). Und in der Timer-Methode aktualisierst du dann dein Wartefenster (ProgressBar, Animation, ... whatever -).
    Und nach dem Befüllen der Controls stoppst du dann den Timer und versteckst wieder das Wartefenster.

    P.S: hartcodierte Pfade (wie "D://Fragenkatalog2.xml") solltest du nicht benutzen.



  • Okay ... versuche das mal umzusetzen.



  • Abend Leute ... wiedermal nen kleines Problem. DataGrid "newDataGrid" enthällt die Daten. Das zweite DataGrid ist "noch leer". Nun möchte ich die Daten "Rows" von der einen in die andere kopieren, aber das gelingt mir nicht:

    Die angegebene Zeile gehört bereits zu einem DataGridView-Steuerelement.

    protected void AfterTloadQuestion(DataGridView newDataGrid)
            {
                DataGridViewRow[] a_row = new DataGridViewRow[newDataGrid.Rows.Count];
                newDataGrid.Rows.CopyTo(a_row, 0);
    
                foreach (DataGridViewRow zeile in a_row) dataGridView1.Rows.Add(zeile);
    
                //Steuerelemente aktivieren
                //dataGridView1.Enabled = true;
                //treeView1.Enabled = true;
    
            }
    


  • Manchmal sieht man den Wald vor lauter Bäumen nicht:

    //Neue Zeilen in ein array kopieren
                DataGridViewRow[] a_row = new DataGridViewRow[newDataGrid.Rows.Count];
                newDataGrid.Rows.CopyTo(a_row, 0);
    
                //newDataGrid entfernen - damit die Zeilen frei werden
                newDataGrid.Dispose();
    
                dataGridView1.Rows.AddRange ( a_row  );
    
                //Steuerelemente aktivieren
                dataGridView1.Enabled = true;
                treeView1.Enabled = true;
    

    Trotzdem Danke ...


Anmelden zum Antworten