2011-01-15 19 views
12

In questo momento ho il codice C# per generare una nuova finestra in un thread diverso, funziona, ma non appena si apre la nuova finestra generata, la finestra si chiude e il filo finisce. Come farò in modo che la nuova finestra generata possa essere chiusa dal primo thread?Crea una nuova discussione per aprire una nuova finestra e chiuderla da una discussione diversa

Ecco un "albero" di come la deposizione delle uova funziona attualmente:

thread principale
--Uses una funzione nel thread principale per avviare un'altra funzione in un thread separato per aprire la finestra w, causando la finestra usare quella discussione

Fondamentalmente voglio solo che le due finestre abbiano ciascuna il proprio thread. E essere in grado di controllare la finestra secondaria generata dal primo thread della finestra.

risposta

15

Questo è solo un breve esempio. È un po 'più robusto del primo che ho scritto. Elimina la condizione di competizione esistente usando p/invoke.

Aggiornamento Ancora avuto una condizione di gara. Questo dovrebbe essere perfetto.

using System; 
using System.Drawing; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Windows.Forms; 

class MainUIThreadForm : Form 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new MainUIThreadForm()); 
    } 

    private IntPtr secondThreadFormHandle; 

    public MainUIThreadForm() 
    { 
     Text = "First UI"; 
     Button button; 
     Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadFormHandle == IntPtr.Zero) 
      { 
       Form form = new Form 
       { 
        Text = "Second UI", 
        Location = new Point(Right, Top), 
        StartPosition = FormStartPosition.Manual, 
       }; 
       form.HandleCreated += SecondFormHandleCreated; 
       form.HandleDestroyed += SecondFormHandleDestroyed; 
       form.RunInNewThread(false); 
      } 
     }; 
     Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadFormHandle != IntPtr.Zero) 
       PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); 
     }; 
    } 

    void EnableStopButton(bool enabled) 
    { 
     if (InvokeRequired) 
      BeginInvoke((Action)(() => EnableStopButton(enabled))); 
     else 
     { 
      Control stopButton = Controls["Stop"]; 
      if (stopButton != null) 
       stopButton.Enabled = enabled; 
     } 
    } 

    void SecondFormHandleCreated(object sender, EventArgs e) 
    { 
     Control second = sender as Control; 
     secondThreadFormHandle = second.Handle; 
     second.HandleCreated -= SecondFormHandleCreated; 
     EnableStopButton(true); 
    } 

    void SecondFormHandleDestroyed(object sender, EventArgs e) 
    { 
     Control second = sender as Control; 
     secondThreadFormHandle = IntPtr.Zero; 
     second.HandleDestroyed -= SecondFormHandleDestroyed; 
     EnableStopButton(false); 
    } 

    const int WM_CLOSE = 0x0010; 
    [DllImport("User32.dll")] 
    extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam); 
} 

internal static class FormExtensions 
{ 
    private static void ApplicationRunProc(object state) 
    { 
     Application.Run(state as Form); 
    } 

    public static void RunInNewThread(this Form form, bool isBackground) 
    { 
     if (form == null) 
      throw new ArgumentNullException("form"); 
     if (form.IsHandleCreated) 
      throw new InvalidOperationException("Form is already running."); 
     Thread thread = new Thread(ApplicationRunProc); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.IsBackground = isBackground; 
     thread.Start(form); 
    } 
} 

Ecco il primo esempio per i posteri:

using System; 
using System.Drawing; 
using System.Threading; 
using System.Windows.Forms; 

class MainUIThreadForm : Form 
{ 
    [STAThread] 
    static void Main() 
    { 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new MainUIThreadForm()); 
    } 

    SecondUIThreadForm secondThreadForm; 
    public MainUIThreadForm() 
    { 
     Text = "First UI"; 
     Button button; 
     Controls.Add(button = new Button { Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) }); 
     button.Click += (s, e) => 
      { 
       if (secondThreadForm == null || !secondThreadForm.IsHandleCreated) 
        secondThreadForm = SecondUIThreadForm.Create(); 
      }; 
     Controls.Add(button = new Button { Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40) }); 
     button.Click += (s, e) => 
     { 
      if (secondThreadForm != null && secondThreadForm.IsHandleCreated) 
       secondThreadForm.Invoke((Action)(() => secondThreadForm.Close())); 
     }; 
    } 
} 

class SecondUIThreadForm : Form 
{ 
    static void Main2(object state) 
    { 
     Application.Run((Form)state); 
    } 

    public static SecondUIThreadForm Create() 
    { 
     SecondUIThreadForm form = new SecondUIThreadForm(); 
     Thread thread = new Thread(Main2); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.Start(form); 
     return form; 
    } 

    public SecondUIThreadForm() 
    { 
     Text = "Second UI"; 
    } 
} 
+0

La chiusura del modulo pulirà correttamente il thread e il modulo? Ho provato a inserire il mio secondo modulo in un "utilizzo" ma ha causato il crash dell'applicazione perché il dispose è stato chiamato dal thread sbagliato – chrispepper1989

+1

@ chrispepper1989 Sembra che il tuo utilizzo non sia in 'ApplicationRunProc' che viene eseguito sul thread che crea il modulo e i suoi controlli. Forse potresti chiederlo come una nuova domanda e linkarlo qui. In questo modo possiamo vedere il codice. Produci un semplice esempio come sopra che duplica il comportamento che stai vedendo. – Tergiver

1

Come è stata creata la nuova finestra dal secondo thread? E cosa fa il thread dopo che la finestra è stata creata?

Senza vedere il codice, direi che il problema è che il secondo thread non pompa i messaggi nella coda dei messaggi di Windows.

Stai chiamando Application.Run sul tuo secondo thread?

BTW: nota che il tuo design ha alcune limitazioni. Il primo thread non sarà in grado di direttamente controllare la seconda finestra. Ogni volta che proverai a manipolare qualsiasi elemento dell'interfaccia utente nella seconda finestra dal primo thread, dovrai utilizzare Control.Invoke per assicurarti che la vera manipolazione dell'interfaccia utente si verifichi sul thread corretto.

26

Scommetto quello che stai facendo è qualcosa di simile:

new Thread(() => new TestForm().Show()).Start(); 

perché questo rende la finestra scompare immediatamente, come si descrive.

Prova a modificare:

new Thread(() => new TestForm().ShowDialog()).Start(); 

ShowDialog gira propria pompa messaggio, e restituisce solo quando la finestra è chiusa.

+0

Soluzione piacevole. Questo ha anche risolto il mio problema quando stavo ottenendo un'eccezione dopo aver chiamato 'Application.Run (form);' sul nuovo thread, che ha impedito la chiusura del modulo sul thread principale. – dahvyd

1

Sto scrivendo un programma, che si avvita e utilizza l'interfaccia utente sul thread creato per spedire disegno funzionalità alla DC.

Quando eseguivamo il porting dell'applicazione per l'esecuzione dal prompt dei comandi, rimanevamo naturalmente con un po 'di problemi dato che il thread del dispatcher non era stato creato o necessario - così ho creato un altro thread dal punto di ingresso dell'app che essenzialmente si chiamava ShowDialog () (l'unico modo per far girare il message pump) sul modulo principale - con OnShown nascosto per nascondere e ridurre al minimo il modulo quando viene effettuata la chiamata.Questo mi ha permesso di inviare ancora al modulo e gestire tutto il mio disegno dagli altri thread multipli.

È certamente un approccio brutto, ma questo è stato un modo rapido per farlo e funziona come previsto.

2

Si può fare in questo modo:

Nel Program.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Windows.Forms; 
using System.Threading; 

namespace TwoWindows 
{ 
    static class Program 
    { 
     public static Form1 form1; 
     public static Form2 form2; 
     /// <summary> 
     /// The main entry point for the application. 
     /// </summary> 
     [STAThread] 
     static void Main() 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 

      form1 = new Form1(); 
      form2 = new Form2(); 

      form1.Form2Property = form2; 
      form2.Form1Property = form1; 

      form1.Show(); 
      form2.Show(); 

      Application.Run(); 
     } 
    } 
} 

In Form1.cs:

namespace TwoWindows 
{ 
    public partial class Form1 : Form 
    { 
     public Form2 Form2Property { get; set; } 

     public Form1() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnClosed(EventArgs e) 
     { 
      if (Form2Property.IsDisposed) 
       Application.Exit(); 
     } 
    } 
} 

E Form2.cs:

namespace TwoWindows 
{ 
    public partial class Form2 : Form 
    { 
     public Form1 Form1Property { get; set; } 

     public Form2() 
     { 
      InitializeComponent(); 
     } 

     protected override void OnClosed(EventArgs e) 
     { 
      if (Form1Property.IsDisposed) 
       Application.Exit(); 
     } 
    } 
} 

In questo modo puoi ottenere due forme sul sam e thread e usarne uno per controllare l'altro. Se è necessario utilizzare i thread, suggerirei di utilizzare thread dedicati che fanno parte delle classi non spawn in un metodo che può essere chiamato più di una volta. Quindi utilizzare ManualResetEvent o AutoResetEvent per controllare l'elaborazione del thread. Mi piace molto l'approccio all'uso di qualcosa di simile, perché è sicuro e non spende molte risorse per inizializzare i thread.

public class MyClassOrForm 
{ 
    Thread myProcessingThread; 
    public AutoResetEvent startProcessing = new AutoResetEvent(false); 
    public AutoResetEvent processingFinished = new AutoResetEvent(false); 
    public AutoResetEvent killProcessingThread = new AutoResetEvent(false); 

    public MyClassOrForm() 
    { 
     myProcessingThread = new Thread(MyProcess); 
    } 

    private void MyProcess() 
    { 
     while (true) 
     { 
      if (startProcessing.WaitOne()) 
      { 
       // Do processing here 

       processingFinished.Set(); 
      } 

      if (killProcessingThread.WaitOne(0)) 
       return; 
     } 
    } 
} 

Poi, dopo aver impostato i dati da elaborare, chiamare formare un'altra classe o un metodo

MyClassOrMethodInstance.startProcessing.Set(); 

E se hai bisogno di aspettare che il trattamento per finire quindi inserire:

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms); 

Questo è equivalente a una chiamata Thread.Join(), solo che non è necessario allocare un altro thread ogni volta con i rischi che i thread comportano se dipendono da dati locali o thread secondario non terminato S.

1

Per un progetto a cui sto lavorando Ho creato un modulo che verrà visualizzato, resterà aperto mentre l'attività è in esecuzione e si chiude in seguito.

Contiene una ProgressBar con le seguenti impostazioni:

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = < - impostare la velocità personalizzato in millisecondi qui

Se si desidera, è possibile impostare il modulo di TopMost proprietà a true.

Ecco il codice per il modulo:

public partial class BusyForm : Form 
{ 
    public BusyForm(string text = "Busy performing action ...") 
    { 
     InitializeComponent(); 
     this.Text = text; 
     this.ControlBox = false; 
    } 

    public void Start() 
    { 
     System.Threading.Tasks.Task.Run(() => 
     { 
      this.ShowDialog(); 
     }); 
    } 

    public void Stop() 
    { 
     BeginInvoke((Action)delegate { this.Close(); }); 
    } 

    public void ChangeText(string newText) 
    { 
     BeginInvoke((Action)delegate { this.Text = newText; }); 
    } 
} 

E qui è il codice per utilizzare il modulo nel codice:

 BusyForm busyForm = new BusyForm(text: "Opening database ..."); 

     busyForm.Start(); 

     //do your stuff here 

     busyForm.Stop(); 

UPDATE: Mi sono imbattuto in alcuni problemi sottostanti con la filettatura. Ecco una versione aggiornata del codice. Per alcune informazioni di base, questo modulo ha una barra di avanzamento che viene mostrata quando un'attività è occupata. Ho aggiunto il comando ChangeText per mostrare un esempio di come è possibile interagire con questo modulo da un altro modulo.Probabilmente dovresti anche menzionare che il tuo Main in Program.cs dovrebbe avere l'attributo [STAThread] come visto di seguito.

[STAThread] 
    static void Main(string[] args) 
    { 
     System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture; 
     Application.EnableVisualStyles(); 
     Application.SetCompatibleTextRenderingDefault(false); 
     Application.Run(new Form1()); 
    } 
Problemi correlati