ここで問題が発生しただけで、修正方法がわかりません。私はGUIとシリアルデータを含む小さなプロジェクトをしています。 GUIはメインスレッドによって実行されており、受信シリアルデータを保持するデータ変数は継続的に更新する必要があるため、これらは2番目のスレッドで更新されています。問題は、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スレッドを使用してtextBoxを更新する方法を示している例です。
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();
}
TestThreadメソッドは、testという名前のスレッドがtextBoxを更新するために使用されます
そこ。
また、WPFを使用してシリアルポートテストツールを開発していますが、私の経験を共有したいと思います。
MVVMの設計パターンに従ってソースコードをリファクタリングする必要があると思います。
初めに、私はあなたが出会ったのと同じ問題に出会い、このコードを使用してそれを解決しました:
new Thread(() =>
{
while (...)
{
SomeTextBox.Dispatcher.BeginInvoke((Action)(() => SomeTextBox.Text = ...));
}
}).Start();
これは機能しますが、見苦しいです。これを見るまで、リファクタリングする方法がわかりません: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
これは、初心者向けの非常に親切なステップバイステップMVVMチュートリアルです。光沢のあるUIも複雑なロジックもありません。MVVMの基本のみです。
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();
});
}
}
次の方法を使用して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 { }));
}
そのときにこのメソッドを使用する場合は、ディスパッチャスレッドから直接同じオブジェクトを更新しないでください。そうしないと、更新された文字列のみが取得され、このメソッドは役に立たなくなります。それでも動作しない場合は、メソッド内のその行をコメント化し、コメントを外したコメントを解除すると、両方ともほぼ同じ効果があり、アクセス方法が異なります。
AkjoshiとJulioが言うように、これは、GUIアイテムと同じスレッド上で、バックグラウンドデータを処理しているメソッドからGUIを更新するアクションをディスパッチすることに関するものです。上記のakjoshiの回答では、このコードを特定の形式で見ることができます。これは一般的なバージョンです。
myTextBlock.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(delegate()
{
myTextBlock.Text = Convert.ToString(myDataObject.getMeData());
}));
重要な部分は、UIオブジェクトのディスパッチャを呼び出すことです。これにより、正しいスレッドが確保されます。
個人的な経験から、このようにアクションをインラインで作成して使用する方がはるかに簡単に思えます。クラスレベルで宣言すると、静的/非静的コンテキストに関する多くの問題が発生しました。
_Dispatcher.BeginInvoke
_を使用する必要があります。私はそれをテストしませんでしたが、 this link(これは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;
}
}
}
_
ここにはいくつかのオプションがあります。
1つは、BackgroundWorkerを使用することです。これは、アプリケーションのマルチスレッド化の一般的なヘルパーです。スレッドプールからのバックグラウンドスレッドで処理されるDoWorkイベントと、バックグラウンドスレッドの完了時にメインスレッドで呼び出されるRunWorkerCompletedイベントを公開します。また、バックグラウンドスレッドで実行中のコードを試行/キャッチして、未処理の例外がアプリケーションを強制終了しないようにするという利点もあります。
そのルートに行きたくない場合は、WPFディスパッチャーオブジェクトを使用してアクションを呼び出し、GUIを更新してメインスレッドに戻すことができます。ランダム参照:
http://www.switchonthecode.com/tutorials/working-with-the-wpf-dispatcher
他にも多くのオプションがありますが、これらは思い浮かぶ最も一般的な2つです。
UIテキストボックスを更新する完全な例を次に示します
<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>
およびコード内
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);
}
}
}