60歳からの電子工作ノート

生涯学習として取り組んでいます。

WPF データバインドで表示したシリアル通信テストプログラム

概要:

 シリアル通信の送受信データ表示に、データバインドを使用したプログラムを作成しました。変数が配列であれば、ObservableCollection として、データバインドの記述が容易ですが、変数が配列でない場合、変数ごとにINotifyPropertyChangedの記述が必要なようです。これを避けるため、ReactivePropertyというライブラリがあります。
INotifyPropertyChangedとReactivePropertyの2種類のデータバインドプログラム、および非データバインドプログラム(TextBlock.Text+=により表示)を作成し、記述の差異を比較しました。

外観:

f:id:vABC:20210925094842p:plain
シリアル通信テストプログラム

処理概要:

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 = "";
        }



    }
}

記述の差異まとめ:

f:id:vABC:20210925124435p:plain

f:id:vABC:20210925125157p:plain
Fig. InotifyPropertyChanged 使用
f:id:vABC:20210925125336p:plain
Fig. ReactivePropertyを使用
f:id:vABC:20210925125407p:plain
Fig. 非データバインドで送受信データ表示

補足

・ 送受信データ表示には、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 )

・テストで使用した回路です。ブレッドボード上でテストしました。

f:id:vABC:20210925133825p:plain
Fig: 回路図