Aggiornamento della GUI (WPF) utilizzando un thread diverso

Ho solo un problema qui che non ho idea di come risolvere. Sto facendo un piccolo progetto che coinvolge una GUI e dati seriali. La GUI viene eseguita dal thread principale e poiché le variabili di dati che contengono i miei dati seriali in entrata devono essere aggiornate continuamente, queste vengono aggiornate in un secondo thread. Il problema è quando ho bisogno di aggiornare alcune caselle di testo sulla GUI, queste devono essere aggiornate con i dati dal thread secondario ed è qui che si trova il mio problema. Non riesco ad aggiornarli direttamente dal thread secondario e non ho idea di come trasferire dati dal mio thread secondario e di elaborare un sistema di aggiornamento dal thread principale. Ho messo il mio codice qui sotto:

Qualsiasi aiuto sarebbe grande.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.IO; using System.IO.Ports; using System.Threading; namespace GUIBike { ///  /// Interaction logic for MainWindow.xaml ///  public partial class MainWindow : Window { public static string inputdata; public static int MaximumSpeed, maximumRiderInput, RiderInput, Time, CurrentSpeed, DistanceTravelled, MaximumMotorOutput, MotorOutput, InputSpeed; public static string SaveDataString; public Thread Serial; public static SerialPort SerialData; public static string[] portlist = SerialPort.GetPortNames(); public static string[] SaveData = new string[4]; public static string directory = "C:\\"; public MainWindow() { Serial = new Thread(ReadData); InitializeComponent(); int Count = 0; for (Count = 0; Count < portlist.Length; Count++) { ComPortCombo.Items.Add(portlist[Count]); } } private void StartDataButton_Click(object sender, RoutedEventArgs e) { SerialData = new SerialPort(ComPortCombo.Text, 19200, Parity.None, 8, StopBits.One); SerialData.Open(); SerialData.WriteLine("P"); Serial.Start(); StartDataButton.IsEnabled = false; EndDataButton.IsEnabled = true; ComPortCombo.IsEnabled = false; CurrentSpeed = 0; MaximumSpeed = 0; Time = 0; DistanceTravelled = 0; MotorOutput = 0; RiderInput = 0; SaveData[0] = ""; SaveData[1] = ""; SaveData[2] = ""; SaveData[3] = ""; SaveDataButton.IsEnabled = false; if (SerialData.IsOpen) { ComPortStatusLabel.Content = "OPEN"; SerialData.NewLine = "/n"; SerialData.WriteLine("0"); SerialData.WriteLine("/n"); } } private void EndDataButton_Click(object sender, RoutedEventArgs e) { SerialData.Close(); SaveDataButton.IsEnabled = true; SerialData.WriteLine("1"); SerialData.WriteLine("0"); if (!SerialData.IsOpen) { ComPortStatusLabel.Content = "CLOSED"; } int i = 0; for (i = 0; i  MaximumSpeed) { MaximumSpeed = CurrentSpeed; } SpeedTextBox.Text = "Current Wheel Speed = " + Convert.ToString(CurrentSpeed) + "Km/h"; DistanceTravelled = DistanceTravelled + (Convert.ToInt16(CurrentSpeed) * Time); DistanceTravelledTextBox.Text = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "Km"; //} //catch (Exception) { } } if (counter == 1) { try { RiderInput = Convert.ToInt16(SerialData.ReadLine()); if (RiderInput > maximumRiderInput) { maximumRiderInput = RiderInput; } RiderInputTextBox.Text = "Current Rider Input Power =" + Convert.ToString(RiderInput) + "Watts"; } catch (Exception) { } } if (counter == 2) { try { MotorOutput = Convert.ToInt16(SerialData.ReadLine()); if (MotorOutput > MaximumMotorOutput) { MaximumMotorOutput = MotorOutput; } MotorOutputTextBox.Text = "Current Motor Output = " + Convert.ToString(MotorOutput) + "Watts"; } catch (Exception) { } } counter++; if (counter == 3) { counter = 0; } } } private void ComPortCombo_SelectionChanged(object sender, SelectionChangedEventArgs e) { StartDataButton.IsEnabled = true; } private void Window_Closed(object sender, RoutedEventArgs e) { if (SerialData.IsOpen) { SerialData.Close(); } } 

È ansible utilizzare un delegato per risolvere questo problema. Ecco un esempio che mostra come aggiornare un textBox usando thread differenti

 public delegate void UpdateTextCallback(string message); private void TestThread() { for (int i = 0; i <= 1000000000; i++) { Thread.Sleep(1000); richTextBox1.Dispatcher.Invoke( new UpdateTextCallback(this.UpdateText), new object[] { i.ToString() } ); } } private void UpdateText(string message) { richTextBox1.AppendText(message + "\n"); } private void button1_Click(object sender, RoutedEventArgs e) { Thread test = new Thread(new ThreadStart(TestThread)); test.Start(); } 

Il metodo TestThread è utilizzato dal thread named test per aggiornare textBox

Là.

Sto anche sviluppando uno strumento per test di porte seriali usando WPF e mi piacerebbe condividere alcune mie esperienze.

Penso che si dovrebbe refactoring il codice sorgente in base al modello di progettazione MVVM.

All’inizio ho incontrato lo stesso problema che hai incontrato e l’ho risolto utilizzando questo codice:

 new Thread(() => { while (...) { SomeTextBox.Dispatcher.BeginInvoke((Action)(() => SomeTextBox.Text = ...)); } }).Start(); 

Funziona, ma è troppo brutto. Non ho idea di come refactoring, fino a quando ho visto questo: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

Questo è un tutorial MVVM molto graduale per principianti. Nessuna interfaccia utente lucida, nessuna logica complessa, solo la base di MVVM.

È ansible utilizzare Dispatcher.Invoke per aggiornare la GUI da un thread secondario.

Ecco un esempio:

  private void Window_Loaded(object sender, RoutedEventArgs e) { new Thread(DoSomething).Start(); } public void DoSomething() { for (int i = 0; i < 100000000; i++) { this.Dispatcher.Invoke(()=>{ textbox.Text=i.ToString(); }); } } 

Utilizzare il seguente metodo per aggiornare la GUI.

  Public Void UpdateUI() { //Here update your label, button or any string related object. //Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate { })); } 

Tienilo presente quando usi questo metodo in quel momento non aggiorna lo stesso object direttamente dal thread del dispatcher, altrimenti ottieni solo quella stringa aggiornata e questo metodo è inutile / inutile. Se ancora non funziona, commenta che la riga all’interno del metodo e il commento non commentato hanno entrambi lo stesso effetto, solo un modo diverso per accedervi.

Come akjoshi e Julio dicono che si tratta di inviare un’azione per aggiornare la GUI sullo stesso thread della voce della GUI ma dal metodo che gestisce i dati in background. Puoi vedere questo codice in forma specifica nella risposta di akjoshi qui sopra. Questa è una versione generale.

 myTextBlock.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { myTextBlock.Text = Convert.ToString(myDataObject.getMeData()); })); 

La parte cruciale è chiamare il dispatcher del tuo object UI – che garantisce di avere il thread corretto.

Dall’esperienza personale sembra molto più facile creare e utilizzare l’azione in linea come questa. Dichiararlo a livello di class mi ha dato molti problemi con contesti statici / non statici.

Devi usare Dispatcher.BeginInvoke . Non l’ho provato ma puoi controllare questo link (questo è lo stesso link fornito da Julio G) per avere una migliore comprensione su come aggiornare i controlli dell’interfaccia utente da thread diversi. Ho modificato il tuo codice ReadData()

 public void ReadData() { int counter = 0; while (SerialData.IsOpen) { if (counter == 0) { //try //{ InputSpeed = Convert.ToInt16(SerialData.ReadChar()); CurrentSpeed = InputSpeed; if (CurrentSpeed > MaximumSpeed) { MaximumSpeed = CurrentSpeed; } SpeedTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { SpeedTextBox.Text = "Current Wheel Speed = " + Convert.ToString(CurrentSpeed) + "Km/h"; });//update GUI from this thread DistanceTravelled = DistanceTravelled + (Convert.ToInt16(CurrentSpeed) * Time); DistanceTravelledTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() {DistanceTravelledTextBox.Text = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "Km"; });//update GUI from this thread //} //catch (Exception) { } } if (counter == 1) { try { RiderInput = Convert.ToInt16(SerialData.ReadLine()); if (RiderInput > maximumRiderInput) { maximumRiderInput = RiderInput; } RiderInputTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { RiderInputTextBox.Text = "Current Rider Input Power =" + Convert.ToString(RiderInput) + "Watts"; });//update GUI from this thread } catch (Exception) { } } if (counter == 2) { try { MotorOutput = Convert.ToInt16(SerialData.ReadLine()); if (MotorOutput > MaximumMotorOutput) { MaximumMotorOutput = MotorOutput; } MotorOutputTextBox.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(delegate() { MotorOutputTextBox.Text = "Current Motor Output = " + Convert.ToString(MotorOutput) + "Watts"; });//update GUI from this thread } catch (Exception) { } } counter++; if (counter == 3) { counter = 0; } } } 

Hai un paio di opzioni qui, penso.

Uno sarebbe utilizzare un BackgroundWorker. Questo è un aiuto comune per il multithreading nelle applicazioni. Espone un evento DoWork che viene gestito su un thread in background dal pool di thread e un evento RunWorkerCompleted che viene richiamato sul thread principale quando il thread in background viene completato. Ha anche il vantaggio di provare / catturare il codice in esecuzione sul thread in background in modo che un’eccezione non gestita non uccida l’applicazione.

Se non si desidera seguire tale percorso, è ansible utilizzare l’object dispatcher WPF per richiamare un’azione per aggiornare la GUI sul thread principale. Riferimento casuale:

http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher

Ci sono anche molte altre opzioni in giro, ma queste sono le due più comuni che vengono in mente.