使用不同的线程更新GUI(WPF)

只是在这里有一个问题,我不知道如何解决。 我正在做一个涉及GUI和串行数据的小型项目。 GUI正在由主线程运行,并且由于保存我的传入串行数据的数据variables需要不断更新,所以在第二个线程中进行更新。 问题是当我需要更新GUI上的一些文本框时,需要使用辅助线程中的数据来更新这些文本框,这就是我的问题所在。 我不能直接从辅助线程更新他们,我不知道如何从我的辅助线程传输数据,并找出一个从主线程更新它们的系统。 我已经把我的代码放在下面:

任何帮助将是伟大的。

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 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> 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 < 4; i++) { if (i == 0) { SaveDataString = "MaximumSpeed during the Ride was = " + Convert.ToString(MaximumSpeed) + "m/h"; SaveData[i] = SaveDataString; } if (i == 1) { SaveDataString = "Total Distance Travelled = " + Convert.ToString(DistanceTravelled) + "m"; SaveData[i] = SaveDataString; } if (i == 2) { SaveDataString = "Maximum Rider Input Power = " + Convert.ToString(maximumRiderInput) + "Watts"; SaveData[i] = SaveDataString; } if (i == 3) { SaveDataString = "Maximum Motor Output Power = " + Convert.ToString(MaximumMotorOutput) + "Watts"; SaveData[i] = SaveDataString; } } } private void SaveDataButton_Click(object sender, RoutedEventArgs e) { //File.WriteAllBytes(directory + "image" + imageNO + ".txt", ); //saves the file to Disk File.WriteAllLines(directory + "BikeData.txt", SaveData); } 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.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(); } } 

您可以使用委托来解决这个问题。 这是一个示例,展示如何使用diffrent线程更新文本框

 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(); } 

线程命名testing使用TestThread方法来更新文本框

那里。

我也在开发一个使用WPF的串口testing工具,我想分享我的一些经验。

我认为你应该根据MVVM的devise模式重构你的源代码。

一开始,我碰到了和你遇到的同样的问题,我用下面的代码解决了这个问题:

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

这工作,但太难看了。 我不知道如何重构它,直到我看到这个: http : //www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial

对于初学者来说,这是一个非常友好的分步MVVM教程。 没有shiny的UI,没有复杂的逻辑,只有MVVM的基础。

使用以下方法更新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 { })); } 

记住当你使用这个方法的时候,不要直接从调度程序线程更新相同的对象,否则你只会得到那个更新的string,这种方法是无奈/无用的。 如果仍然没有工作,那么注释方法里面的那一行和注释一个都有几乎相同的效果,只是不同的方式来访问它。

您可以使用Dispatcher.Invoke从辅助线程更新您的GUI。

这里是一个例子:

  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(); }); } } 

您需要使用Dispatcher.BeginInvoke 。 我没有testing,但你可以检查这个链接(这是由Julio G提供的相同的链接),以更好地了解如何从不同的线程更新UI控件。 我修改了你的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; } } } 

正如akjoshi和Julio所说的那样,这是关于调度一个Action来更新与GUI项目相同的线程的GUI,而是来自处理背景数据的方法。 你可以在上面的akjoshi的回答中以特定的forms看到这个代码。 这是一个通用版本。

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

关键的部分是调用你的UI对象的调度程序 – 确保你有正确的线程。

从个人经验来看,创build和使用内联行为似乎更容易。 在类级别声明它给了我很多静态/非静态上下文的问题。

我想这里有几个select。

一个将是使用BackgroundWorker。 这是应用程序中multithreading的常用帮手。 它公开了一个DoWork事件,该事件在线程池的后台线程上处理,并在后台线程完成时在主线程上调用RunWorkerCompleted事件。 它还具有尝试/捕获在后台线程上运行的代码的好处,以便未处理的exception不会中止应用程序。

如果您不想走这条路线,可以使用WPF调度程序对象来调用一个操作,将GUI更新回主线程。 随机参考:

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

还有很多其他的select,但是这些是最常见的两个想法。