Juste un problème ici que je ne sais pas comment résoudre ce problème. Je fais un petit projet qui implique une interface graphique et des données en série. L'interface graphique est exécutée par le thread principal et, comme les variables de données qui contiennent mes données série entrantes doivent être mises à jour en permanence, elles sont mises à jour dans un second thread. Le problème est que lorsque je dois mettre à jour des zones de texte sur l'interface graphique, celles-ci doivent être mises à jour avec les données du thread secondaire et c'est là que réside mon problème. Je ne peux pas les mettre à jour directement à partir du thread secondaire et je ne sais pas du tout comment transférer des données de mon thread secondaire et créer un système de mise à jour à partir du thread principal. J'ai mis mon code ci-dessous:
Toute aide est la bienvenue.
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();
}
}
Vous pouvez utiliser un délégué pour résoudre ce problème ..___. Voici un exemple qui montre comment mettre à jour une zone de texte à l'aide d'un fil différent
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();
}
La méthode TestThread est utilisée par le thread nommé test pour mettre à jour textBox.
là.
Je développe également un outil de test de port série utilisant WPF, .__, et j'aimerais partager mon expérience.
Je pense que vous devriez refactoriser votre code source en fonction du modèle de conception de MVVM.
Au début, j'ai rencontré le même problème que vous, et je l'ai résolu en utilisant ce code:
new Thread(() =>
{
while (...)
{
SomeTextBox.Dispatcher.BeginInvoke((Action)(() => SomeTextBox.Text = ...));
}
}).Start();
Cela fonctionne, mais est trop moche… .. Je ne sais pas comment le refactoriser, jusqu'à ce que je voie ceci: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick- Start-Tutorial
Ceci est un très gentil tutoriel pas à pas sur MVVM pour les débutants . Pas d’interface utilisateur brillante, pas de logique complexe, seulement la base du MVVM.
Vous pouvez utiliser Dispatcher.Invoke pour mettre à jour votre interface graphique à partir d’un thread secondaire.
Voici un exemple:
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();
});
}
}
Utilisez la méthode suivante pour mettre à jour l'interface graphique.
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 { }));
}
Gardez cela à l'esprit lorsque vous utilisez cette méthode à ce moment-là, ne mettez pas à jour le même objet directement à partir du fil de discussion, sinon vous n'obtiendrez que la chaîne mise à jour et cette méthode est sans défense/inutilisable . Si elle ne fonctionne toujours pas, commentez cette ligne dans la méthode et un-commentaire commenté l'un et l'autre ont presque le même effet, mais une façon différente d'y accéder.
Comme akjoshi et Julio disent qu'il s'agit d'une action pour mettre à jour l'interface graphique sur le même thread que l'élément de l'interface graphique, mais à partir de la méthode qui gère les données d'arrière-plan. Vous pouvez voir ce code sous une forme spécifique dans la réponse de akjoshi ci-dessus. Ceci est une version générale.
myTextBlock.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(delegate()
{
myTextBlock.Text = Convert.ToString(myDataObject.getMeData());
}));
La partie critique est d'appeler le répartiteur de votre objet d'interface utilisateur - cela garantit que vous avez le bon thread.
De par mon expérience personnelle, il semble beaucoup plus facile de créer et d’utiliser l’action inline comme ceci. Déclarer cela au niveau de la classe m'a donné beaucoup de problèmes avec les contextes statiques/non statiques.
Vous devez utiliser Dispatcher.BeginInvoke
. Je ne l'ai pas testé, mais vous pouvez vérifier this link (il s'agit du même lien fourni par Julio G) pour mieux comprendre comment mettre à jour les contrôles de l'interface utilisateur à partir d'un thread différent. J'ai modifié votre code 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;
}
}
}
Vous avez quelques options ici, je pense.
L'une serait d'utiliser un BackgroundWorker. Il s'agit d'une aide courante pour le multithreading dans les applications. Il expose un événement DoWork géré sur un thread en arrière-plan du pool de threads et un événement RunWorkerCompleted qui est rappelé sur le thread principal à la fin du thread en arrière-plan. Cela présente également l'avantage d'essayer de capturer le code en cours d'exécution sur le thread d'arrière-plan afin qu'une exception non gérée ne tue pas l'application.
Si vous ne souhaitez pas emprunter cette route, vous pouvez utiliser l'objet de répartition WPF pour appeler une action afin de remettre l'interface graphique à jour sur le thread principal. Référence aléatoire:
http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
Il existe de nombreuses autres options, mais ce sont les deux plus courantes qui viennent à l’esprit.
Voici un exemple complet qui met à jour les zones de texte de l'interface utilisateur
<Window x:Class="WpfThreading.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfThreading"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="216.84">
<Grid Margin="0,0,2,0">
<Button Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0"
VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<TextBox HorizontalAlignment="Left" Margin="10,35,0,10" TextWrapping="Wrap" Name="mtextBox" Width="87" VerticalScrollBarVisibility="Auto"/>
<TextBox HorizontalAlignment="Left" Margin="111,35,0,10" TextWrapping="Wrap" x:Name="mtextBox2" Width="87" VerticalScrollBarVisibility="Auto"/>
</Grid></Window>
et dans le code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
new Thread(DoSomething).Start();
new Thread(DoSomething2).Start();
}
public void DoSomething()
{
for (int i = 0; i < 100; i++)
{
Dispatcher.BeginInvoke(new Action(() => {
mtextBox.Text += $"{i.ToString()}{Environment.NewLine}";
}), DispatcherPriority.SystemIdle);
Thread.Sleep(100);
}
}
public void DoSomething2()
{
for (int i = 100; i > 0; i--)
{
Dispatcher.BeginInvoke(new Action(() => {
mtextBox2.Text += $"{i.ToString()}{Environment.NewLine}";
}), DispatcherPriority.SystemIdle);
Thread.Sleep(100);
}
}
}