概要:
シリアル通信の送受信データ表示に、データバインドを使用したプログラムを作成しました。変数が配列であれば、ObservableCollection として、データバインドの記述が容易ですが、変数が配列でない場合、変数ごとにINotifyPropertyChangedの記述が必要なようです。これを避けるため、ReactivePropertyというライブラリがあります。
INotifyPropertyChangedとReactivePropertyの2種類のデータバインドプログラム、および非データバインドプログラム(TextBlock.Text+=により表示)を作成し、記述の差異を比較しました。
外観:
処理概要:
16 バイトのデータを1000msec毎に、CH340Eに送信してループバックさせ、送受信データを表示します。また、2バイト目の受信データを表示します。
プログラム:
プログラム 1 (INotifyPropertyChanged)
・画面表示のXAML部分
<Window x:Class="CommTest.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:CommTest" mc:Ignorable="d" ResizeMode="CanResizeWithGrip" Title="CommTest_InotifyPropertyChanged" Height="700" Width="800"> <Grid> <!-- Gridで使用するボタンの大きさ、色を定義--> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Height" Value="30" /> <Setter Property="Width" Value="100"/> <Setter Property="Margin" Value="10" /> <Setter Property="BorderBrush" Value="#a6a6a6" /> <Setter Property="Foreground" Value="#333333" /> <Setter Property="Background" Value="#fcfcfc"/> </Style> </Grid.Resources> <!-- カラム Grid 横方向の大きさ指定。 "AUTO"は、横幅を変更するGridSplitterの部分 --> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*" MinWidth="100"/> <ColumnDefinition Width="AUTO"/> <ColumnDefinition Width="2*" MinWidth="100" /> </Grid.ColumnDefinitions> <!-- Grid 行方向の大きさ指定 "AUTO"は、高さを変更する GridSplitterの部分--> <Grid.RowDefinitions> <RowDefinition Height="1*" MinHeight="100" /> <RowDefinition Height="AUTO" /> <RowDefinition Height="1*" MinHeight="100" /> </Grid.RowDefinitions> <!--横幅を変更する GridSplitter--> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!--高さを変更する GridSplitter--> <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!-- スタックパネル (row=0,col=0) ComboBoxやコントロールを配置--> <StackPanel Grid.Row="0" Grid.Column="0" Margin="10"> <ComboBox x:Name = "ComPortComboBox" TextSearch.TextPath="ComPortName" Height="30" Width="100" Margin="10" BorderBrush="#a6a6a6" Foreground="#333333" Background="#fcfcfc"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding ComPortName}" /> <!--データバインド--> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <Button x:Name= "ComPortOpenButton" Content="Open" Click="ComPortOpenButton_Click"/> <Button x:Name= "ComPortSearchButton" Content="Find" Click="ComPortSearchButton_Click"/> <TextBlock HorizontalAlignment="Center" Margin="10"> ( 76.8 Kbps,1-stop, no-parity) </TextBlock> <TextBox x:Name="OpenInfoTextBox" IsReadOnly="True" BorderThickness="0" Margin ="10" Text ="Open/Close infomation."/> </StackPanel> <!-- スタックパネル (row=2,col=0) 送信ボタンを配置--> <!-- row= 1 には、Gridsplitterが配置されている --> <StackPanel Grid.Row="2" Grid.Column="0" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Send Byte number:"/> <TextBox x:Name="SendByteTextBox" IsReadOnly="False" BorderThickness="1" Margin ="10,0,0,0" Text ="16"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,10,0,0"> <TextBlock Text="Send interval:"/> <TextBox x:Name="SendIntervalTextBox" IsReadOnly="False" BorderThickness="1" Margin ="40,0,0,0" Text ="1000"/> <TextBlock Text="[msec]" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,20,0,0"> <Button Content="Start" Click="Start_Button_Click"/> <Button Content="Stop" Click="Stop_Button_Click"/> <Button Content="Clear" Click="Clear_Button_Click" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,20,0,0"> <TextBlock Text="Received 2nd data:"/> <TextBlock x:Name="Rcv2ndDataTextBlock" Text = "{Binding Rcv2ndData}" Margin ="10,0,0,0" /> </StackPanel> </StackPanel> <!-- ドックパネル (row=0,col=2) TextBoxを配置--> <!-- col= 1 には、Gridsplitterが配置されている --> <DockPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="2" Margin="10"> <TextBlock DockPanel.Dock="Top" Text="Send/Receive Info."/> <ScrollViewer x:Name ="LogTextScroll" VerticalScrollBarVisibility="Auto"> <TextBlock x:Name="SendRcvTextBlock" Text ="{Binding SendRcvmsg}" Margin ="10" /> </ScrollViewer> </DockPanel> </Grid> </Window>
・C#によるコードの部分
using Microsoft.Win32; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; 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.Windows.Threading; // C# 言語バージョン: 7.3 // .NET Framework 4.8 namespace CommTest { // 送受信表示用 ViewModel public class SendRcvMsgClass : INotifyPropertyChanged { string _SendRcvmsg; public string SendRcvmsg // データバインドするプロパティ名 <TextBox x:Name="SendRcvTextBox" Text ="{Binding SendRcvmsg}" { get { return _SendRcvmsg; } set { _SendRcvmsg = value; OnPropertyChanged("SendRcvmsg"); } } public event PropertyChangedEventHandler PropertyChanged; // プロパティの内容が変更された場合のイベント private void OnPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } // 2バイト目の受信データ表示用 public class Rcv2ndDataClass : INotifyPropertyChanged { string _Rcv2ndData; public string Rcv2ndData // データバインドするプロパティ名 { get { return _Rcv2ndData; } set { _Rcv2ndData = value; OnPropertyChanged("Rcv2ndData"); } } public event PropertyChangedEventHandler PropertyChanged; // プロパティの内容が変更された場合のイベント private void OnPropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } // COMポートの コンボボックス用 public class ComPortNameClass { string _ComPortName; public string ComPortName { get { return _ComPortName; } set { _ComPortName = value; } } } /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public ObservableCollection<ComPortNameClass> ComPortNames; // 通信ポート(COM1,COM2等)のコレクション // データバインドするため、ObservableCollection public static SerialPort serialPort; // シリアルポート byte[] sendBuf; // 送信バッファ int sendByteLen; // 送信データのバイト数 int sendInterval; // 送信周期 [msec] byte[] rcvBuf; // 受信バッファ int srcv_pt; // 受信データ格納位置 int data_receive_thread_id; // データ受信ハンドラのスレッドID int data_receive_thread_cnt; // データ受信ハンドラの実施回数 DispatcherTimer SendIntervalTimer; // タイマー モニタ用 電文送信間隔 UInt32 msg_num; // メッセージ番号 public SendRcvMsgClass SendRcvMsgClass; // 送受信データ表示用 ViewModel 宣言 public Rcv2ndDataClass rcv2NdDataClass; // 2バイト目の受信データ表示用 public MainWindow() { InitializeComponent(); MainWindow.serialPort = new SerialPort(); // シリアルポートのインスタンス生成 MainWindow.serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); // データ受信時のイベント処理 ComPortNames = new ObservableCollection<ComPortNameClass>(); // 通信ポートのコレクション インスタンス生成 ComPortComboBox.ItemsSource = ComPortNames; // 通信ポートコンボボックスのアイテムソース指定 SetComPortName(); // 通信ポート名をコンボボックスへ設定 if (ComPortNames.Count > 0) // 通信ポートがある場合 { if (serialPort.IsOpen == true) // new Confserial()実行時に、 既に Openしている場合 { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is opened."; ComPortOpenButton.Content = "Close"; // ボタン表示 Close } else { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is closed."; ComPortOpenButton.Content = "Open"; // ボタン表示 Close } } else { OpenInfoTextBox.Text = "COM port is not found."; } sendBuf = new byte[2048]; // 送信バッファ領域 serialPortのWriteBufferSize =2048 byte(デフォルト) rcvBuf = new byte[4096]; // 受信バッファ領域 SerialPort.ReadBufferSize = 4096 byte (デフォルト SendIntervalTimer = new System.Windows.Threading.DispatcherTimer(); // タイマーの生成 SendIntervalTimer.Tick += new EventHandler(SendIntervalTimer_Tick); // タイマーイベント SendRcvMsgClass = new SendRcvMsgClass(); // 送受信データ表示用 ViewModel インスタンス 作成 SendRcvTextBlock.DataContext = SendRcvMsgClass; // テキストボックスのデータコンテキストに、ViewModel指定 SendRcvMsgClass.SendRcvmsg = ""; // 表示クリア rcv2NdDataClass = new Rcv2ndDataClass(); // 2番目の受信データ表示用 インスタンス作成 Rcv2ndDataTextBlock.DataContext = rcv2NdDataClass; rcv2NdDataClass.Rcv2ndData = ""; } // 通信ポート名をコンボボックスへ設定 private void SetComPortName() { ComPortNames.Clear(); // 通信ポートのコレクション クリア string[] PortList = SerialPort.GetPortNames(); // 存在するシリアルポート名が配列の要素として得られる。 foreach (string PortName in PortList) { ComPortNames.Add(new ComPortNameClass { ComPortName = PortName }); // シリアルポート名の配列を、コレクションへコピー } if (ComPortNames.Count > 0) { ComPortComboBox.SelectedIndex = 0; // 最初のポートを選択 ComPortOpenButton.IsEnabled = true; // ポートOPENボタンを「有効」にする。 } else { ComPortOpenButton.IsEnabled = false; // ポートOPENボタンを「無効」にする。 } } // Findボタンを押した時の処理 // 通信ポートの検索ボタン // private void ComPortSearchButton_Click(object sender, RoutedEventArgs e) { SetComPortName(); } // Openボタンを押した時の処理 // 通信ポートのオープン // // SerialPort.ReadBufferSize = 4096 byte (デフォルト) // WriteBufferSize =2048 byte // private void ComPortOpenButton_Click(object sender, RoutedEventArgs e) { if (serialPort.IsOpen == true) // 既に Openしている場合 { try { serialPort.Close(); OpenInfoTextBox.Text = "Close(" + serialPort.PortName + ")"; ComPortComboBox.IsEnabled = true; // 通信条件等を選択できるようにする。 ComPortSearchButton.IsEnabled = true; // 通信ポート検索ボタンを有効とする。 ComPortOpenButton.Content = "Open"; // ボタン表示を Closeから Openへ } catch (Exception ex) { OpenInfoTextBox.Text = ex.Message; } } else // Close状態からOpenする場合 { serialPort.PortName = ComPortComboBox.Text; // 選択したシリアルポート serialPort.BaudRate = 76800; // ボーレート 76.8[Kbps] serialPort.Parity = Parity.None; // パリティ無し serialPort.StopBits = StopBits.One; // 1 ストップビット serialPort.Open(); // シリアルポートをオープンする serialPort.DiscardInBuffer(); // 受信バッファのクリア ComPortComboBox.IsEnabled = false; // 通信条件等を選択不可にする。 ComPortSearchButton.IsEnabled = false; // 通信ポート検索ボタンを無効とする。 OpenInfoTextBox.Text = " Open (" + serialPort.PortName + ")"; ComPortOpenButton.Content = "Close"; // ボタン表示を OpenからCloseへ } } // // Start ボタン // private void Start_Button_Click(object sender, RoutedEventArgs e) { int.TryParse(SendIntervalTextBox.Text, out sendInterval); // 送信周期を得る SendIntervalTimer.Interval = new TimeSpan(0, 0, 0, 0, sendInterval); // タイマーイベント発生間隔 1000msec msg_num = 0; // メッセージ番号の初期化 SendIntervalTimer.Start(); // 定周期送信用のタイマー開始 } // // Stop ボタン // private void Stop_Button_Click(object sender, RoutedEventArgs e) { SendIntervalTimer.Stop(); //定周期送信用のタイマー停止 } // // 定周期にデータを送信する。 // private void SendIntervalTimer_Tick(object sender, EventArgs e) { Send_TestData(); // テスト用データの 送信 } // // データの送信 // private void Send_TestData() { if (MainWindow.serialPort.IsOpen == true) { srcv_pt = 0; // 受信データ格納位置クリア data_receive_thread_cnt = 0; // int.TryParse(SendByteTextBox.Text, out sendByteLen); // 送信バイト数の入力 for (byte i = 0; i < sendByteLen; i++) { sendBuf[i] = i; } msg_num = msg_num + 1; // メッセージ番号インクリメント MainWindow.serialPort.Write(sendBuf, 0, sendByteLen); // データ送信 SendRcvMsgClass.SendRcvmsg += "Snd:"; // 送信の意味 for (int i = 0; i < sendByteLen; i++) // 送信データの表示 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { SendRcvMsgClass.SendRcvmsg += "\r\n"; } SendRcvMsgClass.SendRcvmsg += sendBuf[i].ToString("X2") + " "; } SendRcvMsgClass.SendRcvmsg += "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")" + "No:" + msg_num.ToString() + "\r\n"; // 時刻と番号 } else { OpenInfoTextBox.Text = "Comm port closed !"; } } // デリゲート関数の宣言 private delegate void DelegateFn(); // データ受信時のイベント処理 private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { int rd_num = MainWindow.serialPort.BytesToRead; // 受信データ数 MainWindow.serialPort.Read(rcvBuf, srcv_pt, rd_num); // 受信データを読み出して、受信バッファに格納 srcv_pt = srcv_pt + rd_num; // 次回の保存位置 data_receive_thread_cnt++; // データ受信ハンドラの実施回数のインクリメント int id = System.Threading.Thread.CurrentThread.ManagedThreadId; data_receive_thread_id = id; //データ受信スレッドのID格納 if (srcv_pt == sendByteLen) // 最終データ受信済み (受信データ数は、送信バイト数と同一とする) イベント処理の終了 { Dispatcher.BeginInvoke(new DelegateFn(RcvProc)); // Delegateを生成して、RcvProcを開始 (表示は別スレッドのため) } } // // データ受信イベント終了時の処理 // 受信データの表示 // private void RcvProc() { string rcv_str = ""; SendRcvMsgClass.SendRcvmsg += "Rcv:"; // 受信の意味 for (int i = 0; i < srcv_pt; i++) // 表示用の文字列作成 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { rcv_str = rcv_str + "\r\n"; } rcv_str = rcv_str + rcvBuf[i].ToString("X2") + " "; } SendRcvMsgClass.SendRcvmsg += rcv_str + "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")(" + srcv_pt.ToString() + " bytes )" + "\r\n"; // 受信文と時刻の表示 rcv2NdDataClass.Rcv2ndData = "0x" + rcvBuf[1].ToString("x2"); // 2バイト目の受信データを、16進数表示 LogTextScroll.ScrollToBottom(); // 一番下までスクロール } // クリアボタンを押した時の処理 private void Clear_Button_Click(object sender, RoutedEventArgs e) { msg_num = 0; // メッセージ番号の初期化 SendRcvMsgClass.SendRcvmsg = ""; rcv2NdDataClass.Rcv2ndData = ""; } } }
プログラム 2 (ReactiveProperty)
・画面表示のXAML部分
<Window x:Class="CommTest.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:CommTest" mc:Ignorable="d" ResizeMode="CanResizeWithGrip" Title="CommTest_ReactiveProperty" Height="600" Width="800"> <Grid> <!-- Gridで使用するボタンの大きさ、色を定義--> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Height" Value="30" /> <Setter Property="Width" Value="100"/> <Setter Property="Margin" Value="10" /> <Setter Property="BorderBrush" Value="#a6a6a6" /> <Setter Property="Foreground" Value="#333333" /> <Setter Property="Background" Value="#fcfcfc"/> </Style> </Grid.Resources> <!-- カラム Grid 横方向の大きさ指定。 "AUTO"は、横幅を変更するGridSplitterの部分 --> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*" MinWidth="100"/> <ColumnDefinition Width="AUTO"/> <ColumnDefinition Width="2*" MinWidth="100" /> </Grid.ColumnDefinitions> <!-- Grid 行方向の大きさ指定 "AUTO"は、高さを変更する GridSplitterの部分--> <Grid.RowDefinitions> <RowDefinition Height="1*" MinHeight="100" /> <RowDefinition Height="AUTO" /> <RowDefinition Height="1*" MinHeight="100" /> </Grid.RowDefinitions> <!--横幅を変更する GridSplitter--> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!--高さを変更する GridSplitter--> <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!-- スタックパネル (row=0,col=0) ComboBoxやコントロールを配置--> <StackPanel Grid.Row="0" Grid.Column="0" Margin="10"> <ComboBox x:Name = "ComPortComboBox" TextSearch.TextPath="ComPortName" Height="30" Width="100" Margin="10" BorderBrush="#a6a6a6" Foreground="#333333" Background="#fcfcfc"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding ComPortName}" /> <!--データバインド--> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <Button x:Name= "ComPortOpenButton" Content="Open" Click="ComPortOpenButton_Click"/> <Button x:Name= "ComPortSearchButton" Content="Find" Click="ComPortSearchButton_Click"/> <TextBlock HorizontalAlignment="Center" Margin="10"> ( 76.8 Kbps,1-stop, no-parity) </TextBlock> <TextBox x:Name="OpenInfoTextBox" IsReadOnly="True" BorderThickness="0" Margin ="10" Text ="Open/Close infomation."/> </StackPanel> <!-- スタックパネル (row=2,col=0) 送信ボタンを配置--> <!-- row= 1 には、Gridsplitterが配置されている --> <StackPanel Grid.Row="2" Grid.Column="0" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Send Byte number:"/> <TextBox x:Name="SendByteTextBox" IsReadOnly="False" BorderThickness="1" Margin ="10,0,0,0" Text ="16"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,10,0,0"> <TextBlock Text="Send interval:"/> <TextBox x:Name="SendIntervalTextBox" IsReadOnly="False" BorderThickness="1" Margin ="40,0,0,0" Text ="1000"/> <TextBlock Text="[msec]" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,20,0,0"> <Button Content="Start" Click="Start_Button_Click"/> <Button Content="Stop" Click="Stop_Button_Click"/> <Button Content="Clear" Click="Clear_Button_Click" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,20,0,0"> <TextBlock Text="Received 2nd data:"/> <TextBlock x:Name="Rcv2ndDataTextBlock" Text = "{Binding Rcv2ndData.Value}" Margin ="10,0,0,0" /> </StackPanel> </StackPanel> <!-- ドックパネル (row=0,col=2) TextBoxを配置--> <!-- col= 1 には、Gridsplitterが配置されている --> <DockPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="2" Margin="10"> <TextBlock DockPanel.Dock="Top" Text="Send/Receive Info."/> <ScrollViewer x:Name ="LogTextScroll" VerticalScrollBarVisibility="Auto"> <TextBlock x:Name="SendRcvTextBlock" Text ="{Binding SendRcvMsg.Value}" Margin ="10" /> </ScrollViewer> </DockPanel> </Grid> </Window>
・C#によるコードの部分
using Microsoft.Win32; using Reactive.Bindings; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; 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.Windows.Threading; // C# 言語バージョン: 7.3 // .NET Framework 4.8 // ReactiveProperty 7.12.0 // namespace CommTest { class ViewModel { // ReactiveProperty<T> のプロパティを宣言 public ReactiveProperty<string> SendRcvMsg { get; set; } // 送受信データ表示用 public ReactiveProperty<string> Rcv2ndData { get; set; } // 2番目の受信データ表示用 public ViewModel() { SendRcvMsg = new ReactiveProperty<string>(); // インスタンスを作成 Rcv2ndData = new ReactiveProperty<string>(); } } // COMポートの コンボボックス用 public class ComPortNameClass { string _ComPortName; public string ComPortName { get { return _ComPortName; } set { _ComPortName = value; } } } /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public ObservableCollection<ComPortNameClass> ComPortNames; // 通信ポート(COM1,COM2等)のコレクション // データバインドするため、ObservableCollection public static SerialPort serialPort; // シリアルポート byte[] sendBuf; // 送信バッファ int sendByteLen; // 送信データのバイト数 int sendInterval; // 送信周期 [msec] byte[] rcvBuf; // 受信バッファ int srcv_pt; // 受信データ格納位置 int data_receive_thread_id; // データ受信ハンドラのスレッドID int data_receive_thread_cnt; // データ受信ハンドラの実施回数 DispatcherTimer SendIntervalTimer; // タイマー モニタ用 電文送信間隔 UInt32 msg_num; // メッセージ番号 ViewModel viewModel; // 宣言 public MainWindow() { InitializeComponent(); MainWindow.serialPort = new SerialPort(); // シリアルポートのインスタンス生成 MainWindow.serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); // データ受信時のイベント処理 ComPortNames = new ObservableCollection<ComPortNameClass>(); // 通信ポートのコレクション インスタンス生成 ComPortComboBox.ItemsSource = ComPortNames; // 通信ポートコンボボックスのアイテムソース指定 SetComPortName(); // 通信ポート名をコンボボックスへ設定 if (ComPortNames.Count > 0) // 通信ポートがある場合 { if (serialPort.IsOpen == true) // new Confserial()実行時に、 既に Openしている場合 { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is opened."; ComPortOpenButton.Content = "Close"; // ボタン表示 Close } else { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is closed."; ComPortOpenButton.Content = "Open"; // ボタン表示 Close } } else { OpenInfoTextBox.Text = "COM port is not found."; } sendBuf = new byte[2048]; // 送信バッファ領域 serialPortのWriteBufferSize =2048 byte(デフォルト) rcvBuf = new byte[4096]; // 受信バッファ領域 SerialPort.ReadBufferSize = 4096 byte (デフォルト SendIntervalTimer = new System.Windows.Threading.DispatcherTimer(); // タイマーの生成 SendIntervalTimer.Tick += new EventHandler(SendIntervalTimer_Tick); // タイマーイベント SendIntervalTimer.Interval = new TimeSpan(0, 0, 0, 0, 1000); // タイマーイベント発生間隔 1000msec msg_num = 0; // メッセージ番号の初期化 viewModel = new ViewModel(); // インスタンス生成 DataContext = viewModel; // ViewModel のインスタンスをビューにバインド } // 通信ポート名をコンボボックスへ設定 private void SetComPortName() { ComPortNames.Clear(); // 通信ポートのコレクション クリア string[] PortList = SerialPort.GetPortNames(); // 存在するシリアルポート名が配列の要素として得られる。 foreach (string PortName in PortList) { ComPortNames.Add(new ComPortNameClass { ComPortName = PortName }); // シリアルポート名の配列を、コレクションへコピー } if (ComPortNames.Count > 0) { ComPortComboBox.SelectedIndex = 0; // 最初のポートを選択 ComPortOpenButton.IsEnabled = true; // ポートOPENボタンを「有効」にする。 } else { ComPortOpenButton.IsEnabled = false; // ポートOPENボタンを「無効」にする。 } } // Findボタンを押した時の処理 // 通信ポートの検索ボタン // private void ComPortSearchButton_Click(object sender, RoutedEventArgs e) { SetComPortName(); } // Openボタンを押した時の処理 // 通信ポートのオープン // // SerialPort.ReadBufferSize = 4096 byte (デフォルト) // WriteBufferSize =2048 byte // private void ComPortOpenButton_Click(object sender, RoutedEventArgs e) { if (serialPort.IsOpen == true) // 既に Openしている場合 { try { serialPort.Close(); OpenInfoTextBox.Text = "Close(" + serialPort.PortName + ")"; ComPortComboBox.IsEnabled = true; // 通信条件等を選択できるようにする。 ComPortSearchButton.IsEnabled = true; // 通信ポート検索ボタンを有効とする。 ComPortOpenButton.Content = "Open"; // ボタン表示を Closeから Openへ } catch (Exception ex) { OpenInfoTextBox.Text = ex.Message; } } else // Close状態からOpenする場合 { serialPort.PortName = ComPortComboBox.Text; // 選択したシリアルポート serialPort.BaudRate = 76800; // ボーレート 76.8[Kbps] serialPort.Parity = Parity.None; // パリティ無し serialPort.StopBits = StopBits.One; // 1 ストップビット serialPort.Open(); // シリアルポートをオープンする serialPort.DiscardInBuffer(); // 受信バッファのクリア ComPortComboBox.IsEnabled = false; // 通信条件等を選択不可にする。 ComPortSearchButton.IsEnabled = false; // 通信ポート検索ボタンを無効とする。 OpenInfoTextBox.Text = " Open (" + serialPort.PortName + ")"; ComPortOpenButton.Content = "Close"; // ボタン表示を OpenからCloseへ } } // // Start ボタン // private void Start_Button_Click(object sender, RoutedEventArgs e) { int.TryParse(SendIntervalTextBox.Text, out sendInterval); // 送信周期を得る SendIntervalTimer.Interval = new TimeSpan(0, 0, 0, 0, sendInterval); // タイマーイベント発生間隔設定 msg_num = 0; // メッセージ番号の初期化 SendIntervalTimer.Start(); // 定周期送信用のタイマー開始 } // // Stop ボタン // private void Stop_Button_Click(object sender, RoutedEventArgs e) { SendIntervalTimer.Stop(); //定周期送信用のタイマー停止 } // // 定周期にデータを送信する。 // private void SendIntervalTimer_Tick(object sender, EventArgs e) { Send_TestData(); // テスト用データの 送信 } // // データの送信 // private void Send_TestData() { if (MainWindow.serialPort.IsOpen == true) { srcv_pt = 0; // 受信データ格納位置クリア data_receive_thread_cnt = 0; // int.TryParse(SendByteTextBox.Text, out sendByteLen); // 送信バイト数の入力 for (byte i = 0; i < sendByteLen; i++) { sendBuf[i] = i; } msg_num = msg_num + 1; // メッセージ番号インクリメント MainWindow.serialPort.Write(sendBuf, 0, sendByteLen); // データ送信 viewModel.SendRcvMsg.Value += "Snd:"; for (int i = 0; i < sendByteLen; i++) // 送信データの表示 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { viewModel.SendRcvMsg.Value += "\r\n"; } viewModel.SendRcvMsg.Value+= sendBuf[i].ToString("X2") + " "; } viewModel.SendRcvMsg.Value += "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")" + "No:" + msg_num.ToString() + "\r\n"; // 時刻と番号 } else { OpenInfoTextBox.Text = "Comm port closed !"; } } // デリゲート関数の宣言 private delegate void DelegateFn(); // データ受信時のイベント処理 private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { int rd_num = MainWindow.serialPort.BytesToRead; // 受信データ数 MainWindow.serialPort.Read(rcvBuf, srcv_pt, rd_num); // 受信データを読み出して、受信バッファに格納 srcv_pt = srcv_pt + rd_num; // 次回の保存位置 data_receive_thread_cnt++; // データ受信ハンドラの実施回数のインクリメント int id = System.Threading.Thread.CurrentThread.ManagedThreadId; data_receive_thread_id = id; //データ受信スレッドのID格納 if (srcv_pt == sendByteLen) // 最終データ受信済み (受信データ数は、送信バイト数と同一とする) イベント処理の終了 { Dispatcher.BeginInvoke(new DelegateFn(RcvProc)); // Delegateを生成して、RcvProcを開始 (表示は別スレッドのため) } } // // データ受信イベント終了時の処理 // 受信データの表示 // private void RcvProc() { string rcv_str = ""; viewModel.SendRcvMsg.Value += "Rcv:"; // 受信の意味 for (int i = 0; i < srcv_pt; i++) // 表示用の文字列作成 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { rcv_str = rcv_str + "\r\n"; } rcv_str = rcv_str + rcvBuf[i].ToString("X2") + " "; } viewModel.SendRcvMsg.Value += rcv_str + "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")(" + srcv_pt.ToString() + " bytes )" + "\r\n"; // 受信文と時刻の表示 viewModel.Rcv2ndData.Value = "0x" + rcvBuf[1].ToString("x2"); // 2バイト目の受信データを、16進数表示 LogTextScroll.ScrollToBottom(); // 一番下までスクロール } // クリアボタンを押した時の処理 private void Clear_Button_Click(object sender, RoutedEventArgs e) { msg_num = 0; // メッセージ番号の初期化 viewModel.SendRcvMsg.Value = ""; viewModel.Rcv2ndData.Value = ""; } } }
プログラム 3 (非データバインド)(TextBlock.Text += で表示)
・画面表示のXAML部分
<Window x:Class="CommTest.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:CommTest" mc:Ignorable="d" ResizeMode="CanResizeWithGrip" Title="CommTest TextBlock.Text+=" Height="700" Width="800"> <Grid> <!-- Gridで使用するボタンの大きさ、色を定義--> <Grid.Resources> <Style TargetType="Button"> <Setter Property="Height" Value="30" /> <Setter Property="Width" Value="100"/> <Setter Property="Margin" Value="10" /> <Setter Property="BorderBrush" Value="#a6a6a6" /> <Setter Property="Foreground" Value="#333333" /> <Setter Property="Background" Value="#fcfcfc"/> </Style> </Grid.Resources> <!-- カラム Grid 横方向の大きさ指定。 "AUTO"は、横幅を変更するGridSplitterの部分 --> <Grid.ColumnDefinitions> <ColumnDefinition Width="1*" MinWidth="100"/> <ColumnDefinition Width="AUTO"/> <ColumnDefinition Width="2*" MinWidth="100" /> </Grid.ColumnDefinitions> <!-- Grid 行方向の大きさ指定 "AUTO"は、高さを変更する GridSplitterの部分--> <Grid.RowDefinitions> <RowDefinition Height="1*" MinHeight="100" /> <RowDefinition Height="AUTO" /> <RowDefinition Height="1*" MinHeight="100" /> </Grid.RowDefinitions> <!--横幅を変更する GridSplitter--> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!--高さを変更する GridSplitter--> <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Gainsboro"/> <!-- スタックパネル (row=0,col=0) ComboBoxやコントロールを配置--> <StackPanel Grid.Row="0" Grid.Column="0" Margin="10"> <ComboBox x:Name = "ComPortComboBox" TextSearch.TextPath="ComPortName" Height="30" Width="100" Margin="10" BorderBrush="#a6a6a6" Foreground="#333333" Background="#fcfcfc"> <ComboBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" > <TextBlock Text="{Binding ComPortName}" /> <!--データバインド--> </StackPanel> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> <Button x:Name= "ComPortOpenButton" Content="Open" Click="ComPortOpenButton_Click"/> <Button x:Name= "ComPortSearchButton" Content="Find" Click="ComPortSearchButton_Click"/> <TextBlock HorizontalAlignment="Center" Margin="10"> ( 76.8 Kbps,1-stop, no-parity) </TextBlock> <TextBox x:Name="OpenInfoTextBox" IsReadOnly="True" BorderThickness="0" Margin ="10" Text ="Open/Close infomation."/> </StackPanel> <!-- スタックパネル (row=2,col=0) 送信ボタンを配置--> <!-- row= 1 には、Gridsplitterが配置されている --> <StackPanel Grid.Row="2" Grid.Column="0" Margin="10"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Send Byte number:"/> <TextBox x:Name="SendByteTextBox" IsReadOnly="False" BorderThickness="1" Margin ="10,0,0,0" Text ="16"/> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,10,0,0"> <TextBlock Text="Send interval:"/> <TextBox x:Name="SendIntervalTextBox" IsReadOnly="False" BorderThickness="1" Margin ="40,0,0,0" Text ="1000"/> <TextBlock Text="[msec]" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Vertical" Margin="0,20,0,0"> <Button Content="Start" Click="Start_Button_Click"/> <Button Content="Stop" Click="Stop_Button_Click"/> <Button Content="Clear" Click="Clear_Button_Click" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0,20,0,0"> <TextBlock Text="Received 2nd data:"/> <TextBlock x:Name="Rcv2ndDataTextBlock" Margin ="10,0,0,0" Text ="xx"/> </StackPanel> </StackPanel> <!-- ドックパネル (row=0,col=2) TextBoxを配置--> <!-- col= 1 には、Gridsplitterが配置されている --> <DockPanel Grid.Row="0" Grid.RowSpan="3" Grid.Column="2" Margin="10"> <TextBlock DockPanel.Dock="Top" Text="Send/Receive Info."/> <ScrollViewer x:Name ="LogTextScroll" VerticalScrollBarVisibility="Auto"> <TextBlock x:Name="SendRcvTextBlock" Margin ="10" Text ="" /> </ScrollViewer> </DockPanel> </Grid> </Window>
・C#によるコードの部分
using Microsoft.Win32; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading.Tasks; 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.Windows.Threading; // // C# 言語バージョン: 7.3 // NET Framework 4.8 namespace CommTest { // COMポートの コンボボックス用 public class ComPortNameClass { string _ComPortName; public string ComPortName { get { return _ComPortName; } set { _ComPortName = value; } } } /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public ObservableCollection<ComPortNameClass> ComPortNames; // 通信ポート(COM1,COM2等)のコレクション // データバインドするため、ObservableCollection public static SerialPort serialPort; // シリアルポート byte[] sendBuf; // 送信バッファ int sendByteLen; // 送信データのバイト数 int sendInterval; // 送信周期 [msec] byte[] rcvBuf; // 受信バッファ int srcv_pt; // 受信データ格納位置 int data_receive_thread_id; // データ受信ハンドラのスレッドID int data_receive_thread_cnt; // データ受信ハンドラの実施回数 DispatcherTimer SendIntervalTimer; // タイマー モニタ用 電文送信間隔 UInt32 msg_num; // メッセージ番号 public MainWindow() { InitializeComponent(); MainWindow.serialPort = new SerialPort(); // シリアルポートのインスタンス生成 MainWindow.serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler); // データ受信時のイベント処理 ComPortNames = new ObservableCollection<ComPortNameClass>(); // 通信ポートのコレクション インスタンス生成 ComPortComboBox.ItemsSource = ComPortNames; // 通信ポートコンボボックスのアイテムソース指定 SetComPortName(); // 通信ポート名をコンボボックスへ設定 if (ComPortNames.Count > 0) // 通信ポートがある場合 { if (serialPort.IsOpen == true) // new Confserial()実行時に、 既に Openしている場合 { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is opened."; ComPortOpenButton.Content = "Close"; // ボタン表示 Close } else { OpenInfoTextBox.Text = "(" + serialPort.PortName + ") is closed."; ComPortOpenButton.Content = "Open"; // ボタン表示 Close } } else { OpenInfoTextBox.Text = "COM port is not found."; } sendBuf = new byte[2048]; // 送信バッファ領域 serialPortのWriteBufferSize =2048 byte(デフォルト) rcvBuf = new byte[4096]; // 受信バッファ領域 SerialPort.ReadBufferSize = 4096 byte (デフォルト SendIntervalTimer = new System.Windows.Threading.DispatcherTimer(); // タイマーの生成 SendIntervalTimer.Tick += new EventHandler(SendIntervalTimer_Tick); // タイマーイベント } // 通信ポート名をコンボボックスへ設定 private void SetComPortName() { ComPortNames.Clear(); // 通信ポートのコレクション クリア string[] PortList = SerialPort.GetPortNames(); // 存在するシリアルポート名が配列の要素として得られる。 foreach (string PortName in PortList) { ComPortNames.Add(new ComPortNameClass { ComPortName = PortName }); // シリアルポート名の配列を、コレクションへコピー } if (ComPortNames.Count > 0) { ComPortComboBox.SelectedIndex = 0; // 最初のポートを選択 ComPortOpenButton.IsEnabled = true; // ポートOPENボタンを「有効」にする。 } else { ComPortOpenButton.IsEnabled = false; // ポートOPENボタンを「無効」にする。 } } // Findボタンを押した時の処理 // 通信ポートの検索ボタン // private void ComPortSearchButton_Click(object sender, RoutedEventArgs e) { SetComPortName(); } // Openボタンを押した時の処理 // 通信ポートのオープン // // SerialPort.ReadBufferSize = 4096 byte (デフォルト) // WriteBufferSize =2048 byte // private void ComPortOpenButton_Click(object sender, RoutedEventArgs e) { if (serialPort.IsOpen == true) // 既に Openしている場合 { try { serialPort.Close(); OpenInfoTextBox.Text = "Close(" + serialPort.PortName + ")"; ComPortComboBox.IsEnabled = true; // 通信条件等を選択できるようにする。 ComPortSearchButton.IsEnabled = true; // 通信ポート検索ボタンを有効とする。 ComPortOpenButton.Content = "Open"; // ボタン表示を Closeから Openへ } catch (Exception ex) { OpenInfoTextBox.Text = ex.Message; } } else // Close状態からOpenする場合 { serialPort.PortName = ComPortComboBox.Text; // 選択したシリアルポート serialPort.BaudRate = 76800; // ボーレート 76.8[Kbps] serialPort.Parity = Parity.None; // パリティ無し serialPort.StopBits = StopBits.One; // 1 ストップビット serialPort.Open(); // シリアルポートをオープンする serialPort.DiscardInBuffer(); // 受信バッファのクリア ComPortComboBox.IsEnabled = false; // 通信条件等を選択不可にする。 ComPortSearchButton.IsEnabled = false; // 通信ポート検索ボタンを無効とする。 OpenInfoTextBox.Text = " Open (" + serialPort.PortName + ")"; ComPortOpenButton.Content = "Close"; // ボタン表示を OpenからCloseへ } } // // Start ボタン // private void Start_Button_Click(object sender, RoutedEventArgs e) { int.TryParse(SendIntervalTextBox.Text, out sendInterval); // 送信周期を得る SendIntervalTimer.Interval = new TimeSpan(0, 0, 0, 0, sendInterval); // タイマーイベント発生間隔設定 msg_num = 0; // メッセージ番号の初期化 SendIntervalTimer.Start(); // 定周期送信用のタイマー開始 } // // Stop ボタン // private void Stop_Button_Click(object sender, RoutedEventArgs e) { SendIntervalTimer.Stop(); //定周期送信用のタイマー停止 } // // 定周期にデータを送信する。 // private void SendIntervalTimer_Tick(object sender, EventArgs e) { Send_TestData(); // テスト用データの 送信 } // // データの送信 // private void Send_TestData() { if (MainWindow.serialPort.IsOpen == true) { srcv_pt = 0; // 受信データ格納位置クリア data_receive_thread_cnt = 0; // int.TryParse(SendByteTextBox.Text, out sendByteLen); // 送信バイト数の入力 for (byte i = 0; i < sendByteLen; i++) { sendBuf[i] = i; } msg_num = msg_num + 1; // メッセージ番号インクリメント MainWindow.serialPort.Write(sendBuf, 0, sendByteLen); // データ送信 SendRcvTextBlock.Text += "Snd:"; // 送信の意味 for (int i = 0; i < sendByteLen; i++) // 送信データの表示 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { SendRcvTextBlock.Text += "\r\n"; } SendRcvTextBlock.Text += sendBuf[i].ToString("X2") + " "; } SendRcvTextBlock.Text += "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")" + "No:" + msg_num.ToString()+ "\r\n"; // 時刻と番号 } else { OpenInfoTextBox.Text = "Comm port closed !"; } } // デリゲート関数の宣言 private delegate void DelegateFn(); // データ受信時のイベント処理 private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { int rd_num = MainWindow.serialPort.BytesToRead; // 受信データ数 MainWindow.serialPort.Read(rcvBuf, srcv_pt, rd_num); // 受信データを読み出して、受信バッファに格納 srcv_pt = srcv_pt + rd_num; // 次回の保存位置 data_receive_thread_cnt++; // データ受信ハンドラの実施回数のインクリメント int id = System.Threading.Thread.CurrentThread.ManagedThreadId; data_receive_thread_id = id; //データ受信スレッドのID格納 if (srcv_pt == sendByteLen) // 最終データ受信済み (受信データ数は、送信バイト数と同一とする) イベント処理の終了 { Dispatcher.BeginInvoke(new DelegateFn(RcvProc)); // Delegateを生成して、RcvProcを開始 (表示は別スレッドのため) } } // // データ受信イベント終了時の処理 // 受信データの表示 // private void RcvProc() { string rcv_str = ""; SendRcvTextBlock.Text += "Rcv:"; // 受信の意味 for (int i = 0; i < srcv_pt; i++) // 表示用の文字列作成 { if ((i > 0) && (i % 16 == 0)) // 16バイト毎に1行空ける { rcv_str = rcv_str + "\r\n"; } rcv_str = rcv_str + rcvBuf[i].ToString("X2") + " "; } SendRcvTextBlock.Text += rcv_str; // 受信文 SendRcvTextBlock.Text += "(" + DateTime.Now.ToString("HH:mm:ss.fff") + ")(" + srcv_pt.ToString() + " bytes )" + "\r\n"; Rcv2ndDataTextBlock.Text = "0x" + rcvBuf[1].ToString("x2"); // 2バイト目の受信データを、16進数表示 LogTextScroll.ScrollToBottom(); // 一番下までスクロール } // クリアボタンを押した時の処理 private void Clear_Button_Click(object sender, RoutedEventArgs e) { msg_num = 0; // メッセージ番号の初期化 SendRcvTextBlock.Text = ""; Rcv2ndDataTextBlock.Text = ""; } } }
記述の差異まとめ:
補足
・ 送受信データ表示には、TextBlockを使用しています。 TextBoxでは、大量のデータを表示した場合、遅くなるようです。
・ ReactivePropertyは、NuGetパッケージの管理によるインストールが必要です。
「ReactiveProperty is MVVM and Asynchronous Extensions for Reactive Extensions(System.Reactive). Target platform is .NET Standard 2.0.」を使用しました。
・開発環境は以下の通りです。
Win10
Microsoft Visual Studio Community 2019 Version 16.11.3 により、WPFアプリ(.NET Framework) の開発
.NET Framework 4.8
( ライブラリ ReactiveProperty 7.12.0 )
・テストで使用した回路です。ブレッドボード上でテストしました。