パソコンでWindowsプログラムを作成するときに、Visual Studioのツールボックスからコントロールをドラッグ&ドロップで、Windowsフォーム上に配置する方法は、パソコン画面を見ながら各コントロールを配置できるので、画面を作成し易くなります。Windowsフォームを動的に切り替えて、様々なデータを入力してメッセージに編集するシリアル転送プログラムを作成します。

動作環境

  • Windows 10 Professional
  • Visual Studio 2019

作成するシリアル転送プログラムについて

シリアル転送プログラムの仕様

2階層のメニューがあり、それぞれ入力するデータが異なります。入力されたデータをメッセージに編集し、シリアルインタフェースで相手装置に送信します。相手装置からは、そのメッセージの応答が送信されます。

フォームデザイン方針

2階層のメニューは、上位のメニューはラジオボタンで表現し、下位のメニューはコンポボックスで表現します。メニューごとに異なるコントロールを抽出してパネル上に配置し、これをテンプレートとして一つのフォーム上にメニューごとのパネルを配置します。出来上がったパネルの情報の部分だけを関数化して、プログラム実行時に呼び出し、以降、ラジオボタンで切り替えるごとに、対応するパネルを画面に表示します。コンポボックスでメニューを切り替えるごとに、各コントロールの有効/無効を制御します。

シリアル転送プログラムの画面の作成手順

フォームデザイン方針に従って、次の手順で今回のプログラム「シリアル転送プログラム」の画面ーGUIを作成します。

  1. 作成したプログラムの画面を次に示します。


  2. テンプレートとして作成したメニューごとのコントロール配置を次に示します。


  3. テンプレートのフォームからパネルの情報の部分だけを切り出して関数化したコードを次に示します。テンプレートフレーム自身は削除し、各パネルの配置のコード(25行目と66行目)をコメントします。
  4. rivate void InitializeTemplate()
    {
        this.PnelExec = new System.Windows.Forms.Panel();
        this.txbDist = new System.Windows.Forms.TextBox();
        this.cmbArea = new System.Windows.Forms.ComboBox();
        this.label1 = new System.Windows.Forms.Label();
        this.label2 = new System.Windows.Forms.Label();
        this.pnlSet = new System.Windows.Forms.Panel();
        this.txbTemp = new System.Windows.Forms.TextBox();
        this.cmbFlower = new System.Windows.Forms.ComboBox();
        this.txbCount = new System.Windows.Forms.TextBox();
        this.label4 = new System.Windows.Forms.Label();
        this.label8 = new System.Windows.Forms.Label();
        this.label10 = new System.Windows.Forms.Label();
        this.PnelExec.SuspendLayout();
        this.pnlSet.SuspendLayout();
        this.SuspendLayout();
        // 
        // pnlExec
        // 
        this.PnelExec.Controls.Add(this.txbDist);
        this.PnelExec.Controls.Add(this.cmbArea);
        this.PnelExec.Controls.Add(this.label1);
        this.PnelExec.Controls.Add(this.label2);
        //this.PnelExec.Location = new System.Drawing.Point(28, 22);
        this.PnelExec.Location = new System.Drawing.Point(0, 0);
        this.PnelExec.Name = "pnlExec";
        this.PnelExec.Size = new System.Drawing.Size(387, 72);
        this.PnelExec.TabIndex = 6;
        // 
        // txbDist
        // 
        this.txbDist.Location = new System.Drawing.Point(46, 12);
        this.txbDist.Name = "txbDist";
        this.txbDist.Size = new System.Drawing.Size(55, 19);
        this.txbDist.TabIndex = 2;
        // 
        // cmbArea
        // 
        this.cmbArea.FormattingEnabled = true;
        this.cmbArea.Items.AddRange(new object[] {
        "エリアA",
        "エリアB",
        "エリアC"});
        this.cmbArea.Location = new System.Drawing.Point(151, 12);
        this.cmbArea.Name = "cmbArea";
        this.cmbArea.Size = new System.Drawing.Size(102, 20);
        this.cmbArea.TabIndex = 4;
        // 
        // label1
        // 
        this.label1.AutoSize = true;
    ・・・
       this.label2.Size = new System.Drawing.Size(29, 12);
        this.label2.TabIndex = 3;
        this.label2.Text = "領域";
        // 
        // pnlSet
        // 
        this.pnlSet.Controls.Add(this.txbTemp);
        this.pnlSet.Controls.Add(this.cmbFlower);
        this.pnlSet.Controls.Add(this.txbCount);
        this.pnlSet.Controls.Add(this.label4);
        this.pnlSet.Controls.Add(this.label8);
        this.pnlSet.Controls.Add(this.label10);
        //this.pnlSet.Location = new System.Drawing.Point(28, 117);
        this.pnlSet.Location = new System.Drawing.Point(0, 0);
        this.pnlSet.Name = "pnlSet";
        this.pnlSet.Size = new System.Drawing.Size(387, 72);
        this.pnlSet.TabIndex = 6;
        // 
        // txbTemp
        // 
        this.txbTemp.Location = new System.Drawing.Point(151, 38);
        this.txbTemp.Name = "txbTemp";
        this.txbTemp.Size = new System.Drawing.Size(55, 19);
        this.txbTemp.TabIndex = 2;
        // 
        // cmbFlower
        // 
        this.cmbFlower.FormattingEnabled = true;
    ・・・
        this.label10.Size = new System.Drawing.Size(17, 12);
        this.label10.TabIndex = 3;
        this.label10.Text = "花";
    }
    
  5. シリアル転送プログラムコードを次に示します。
  6. private Serial serial;
    
    int read = 0;
    byte[] data;
    
    private int btnID;
    private string[] execMessage = {
        "exec-0",
        "exec-1 ",
        "exec-2"};
    private string[] setMessage = {
        "set-0",
        "set-1 ",
        "set-2"};
    
    public Panel()
    {
        InitializeComponent();
    
        InitializeTemplate();
        cmbMessage.SelectedIndex = 0;
        btnSend.Enabled = false;
        btnID = 0;
        this.pnlInput.Controls.Add(this.PnelExec);
        cmbArea.SelectedIndex = 0;
        ExecMessage();
    
        serial = new Serial();
    }
    
    private void btnSend_Click(object sender, EventArgs e)
    {
        string outdata = null;
    
        switch (btnID)
        {
            case 0:
                outdata = ExecSendData();
                break;
            case 1:
                outdata = SetSendData();
                break;
            default:
                break;
        }
        serial.writeserial(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss ") + outdata + "\r\n");
    }
    
    private void cmbMessage_SelectedIndexChanged(object sender, EventArgs e)
    {
        switch (btnID)
        {
            case 0:
                ExecMessage();
                break;
            case 1:
                SetMessage();
                break;
            default:
                break;
        }
    }
    
    private void rdoExec_CheckedChanged(object sender, EventArgs e)
    {
        if (rdoExec.Checked)
        {
            btnID = 0;
            this.pnlInput.Controls.Clear();
            this.pnlInput.Controls.Add(this.PnelExec);
            this.cmbMessage.Items.Clear();
            this.cmbMessage.Items.AddRange(execMessage);
            cmbArea.SelectedIndex = 0;
            cmbMessage.SelectedIndex = 0;
        }
    }
    
    private void rdoSet_CheckedChanged(object sender, EventArgs e)
    {
        if (rdoSet.Checked)
        {
            btnID = 1;
            this.pnlInput.Controls.Clear();
            this.pnlInput.Controls.Add(this.pnlSet);
            this.cmbMessage.Items.Clear();
            this.cmbMessage.Items.AddRange(setMessage);
            cmbFlower.SelectedIndex = 0;
            cmbMessage.SelectedIndex = 0;
        }
    }
    
    private async void rdoconnect_CheckedChanged(object sender, EventArgs e)
    {
        if (rdoconnect.Checked)
        {
            try
            {
                int id = System.Threading.Thread.CurrentThread.ManagedThreadId;
                Console.WriteLine("btnconnect_Click1 ThreadID : " + id);
    
                Console.Write("btnconnect_Click1\n");
                serial.openserial("COM11");
                btnSend.Enabled = true;
    
                while (true)
                {
                    await Task.Run(() => serial.readserial(out read, out data));
                    Console.Write("btnconnect_Click2\n");
    
                    id = System.Threading.Thread.CurrentThread.ManagedThreadId;
                    Console.WriteLine("btnconnect_Click2 ThreadID : " + id);
    
                    txbLog.AppendText(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss") + " readserial len: " + read + " data: " +
                        BitConverter.ToString(data) + "\r\n");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
    }
    
    private void rdodisconnect_CheckedChanged(object sender, EventArgs e)
    {
        if (rdodisconnect.Checked)
        {
            serial.closeserial();
            btnSend.Enabled = false;
        }
    }
    
  7. 2階層メニュー処理は、上位のメニューを左側のラジオボタンに割り当てます。番号4のシリアル転送プログラムコードの64行目から76行目が実行コマンドボタン、78行目から88行目が設定コマンドボタンが押された時の処理を示します。ここでは、テンプレートで作成したパネルから選択して表示します。下位のメニューは上部に配置しているコンポボックスに割り当てます。番号4のシリアル転送プログラムコードの49行目から62行目でラジオボタンの状態により、表示されているコントロールの有効無効を次のExecMessageメソッドあるいはSetMessageメソッドで制御しています。
  8. private void ExecMessage()
    {
        switch (cmbMessage.SelectedIndex)
        {
            case 0:
                txbDist.Enabled = true;
                cmbArea.Enabled = true;
                break;
            case 1:
                txbDist.Enabled = false;
                cmbArea.Enabled = true;
                break;
            case 2:
                txbDist.Enabled = true;
                cmbArea.Enabled = false;
                break;
            default:
                break;
        }
    }
    private void SetMessage()
    {
        switch (cmbMessage.SelectedIndex)
        {
            case 0:
                txbCount.Enabled = true;
                txbTemp.Enabled = true;
                cmbFlower.Enabled = true;
                break;
            case 1:
                txbCount.Enabled = false;
                txbTemp.Enabled = true;
                cmbFlower.Enabled = false;
                break;
            case 2:
                txbCount.Enabled = true;
                txbTemp.Enabled = false;
                cmbFlower.Enabled = true;
                break;
            default:
                break;
        }
    }
    private string ExecSendData()
    {
        string outdata = null;
    
        switch (cmbMessage.SelectedIndex)
        {
            case 0:
                outdata = txbDist.Text+cmbArea.SelectedItem.ToString();
                break;
            case 1:
                 outdata = cmbArea.SelectedItem.ToString();
              break;
            case 2:
                outdata = txbDist.Text;
                break;
            default:
                break;
        }
        return outdata;
    }
    private string  SetSendData()
    {
        string outdata = null;
        switch (cmbMessage.SelectedIndex)
        {
            case 0:
                outdata = txbCount.Text + txbTemp.Text +cmbFlower.SelectedItem.ToString();
                break;
            case 1:
                outdata = txbTemp.Text;
               break;
            case 2:
                 outdata = txbCount.Text  +cmbFlower.SelectedItem.ToString();
               break;
            default:
                break;
        }
        return outdata;
    }
    

シリアル転送クラスの作成

シリアル転送クラスは「C#Taskクラスを使ってシリアル通信の非同期処理」を参照し、クラス化「クラス名:Serial」して送信メソッドを追加します。また、「com0comによるシリアル接続の試験環境の構築」に示す仮想シリアルポートを使用して、シリアル転送の試験を行います。
番号4のシリアル転送プログラムコードの28行目でシリアル転送クラスをインスタンス化し、接続ボタンを押すと92行目から122行目でシリアル回線をオープン(openserialメソッド)してデータを読み込み(readserialメソッド)ます。データの読み込みはTask命令により非同期動作としました。送信ボタンを押すと、31行目から47行目でラジオボタンの選択状況に応じてデータを入力し、メッセージに編集して送信(writeserialメソッド)します。なおメッセージの編集については、番号5の44行目から82行目に示す ExecSendDataメソッドやSetSendDataメソッドが呼び出されます。

シリアル転送プログラムの実行

作成したシリアル転送プログラムを起動し、設定コマンドボタンを押して、コンポボックスで「set-1」を選択し、接続ボタンを押すと次のように設定できるコントロール、この場合「温度」のみが設定可能となります。

TeraTermアプリを起動し、シリアルで「COM12」に接続します。温度に「11」を設定して送信ボタンを押すと、TeraTermの画面に「xxxx 11」(xxxx:日時)が表示されます。TeraTermアプリから「TomoSoft」を入力すると、画面下のテキストボックスに次のように表示されます。仕様上、9バイトごとバイナリで16進数表示します。