WPFでシリアルポートの選択ボックスを実装する

こういうやつ
ちゃんとポートの抜き差しも反映されるよ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260065/5e81c9d0-4a6f-9510-6e77-6afe6fb9d14f.png)


# プロジェクトの作成

新規作成→プロジェクトでWPFアプリ(.NET Framawork)を選択、プロジェクトを作成します。
プロジェクト名はSerialPortComboBoxとします。

# 

ツールボックスから、ComboBoxとButtonを配置する。
名前をそれぞれ、SerialPortComboBoxとConnectButtonに変更

```MainWindow.xaml
<Window x:Class="SerialPortComboBox.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:SerialPortComboBox"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ComboBox x:Name="SerialPortComboBox" HorizontalAlignment="Left" Margin="168,50,0,0" VerticalAlignment="Top" Width="120" SelectionChanged="comboBox_SelectionChanged"/>
        <Button x:Name="ConnectButton" Content="Connect" HorizontalAlignment="Left" Margin="300,53,0,0" VerticalAlignment="Top" Width="75" Click="ConnectButton_Click"/>
    </Grid>
</Window>

```

# 実装

ソリューションエクスプローラーで右クリック、追加→新しい項目で、クラスファイルを作成。ファイル名をSerialPortComboBox.csとする

```SerialPortComboBox.cs
using System;
using System.Windows.Controls;
using System.IO.Ports;
using System.Windows.Threading;
using System.ComponentModel;

using SerialPortComboBox;

delegate void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e);
static class COMPortSelector
{
    public static SerialPort port;
    private static int BAUDRATE = 1000000;
    private static MainWindow mainWindow;
    private static ComboBox SerialPortComboBox;
    private static Button ConnectButton;
    private static DispatcherTimer _timer;
    private static bool isConnected = false;


    private static DataReceivedHandler data_received_handle_;

    public static void Init()
    {
        mainWindow = (MainWindow)App.Current.MainWindow;
        SerialPortComboBox = mainWindow.SerialPortComboBox;
        ConnectButton = mainWindow.ConnectButton;
        SerialPortComboBox.SelectedIndex = 0;
        SetTimer();
    }
    public static void SetBaudrate(int baudrate)
    {
        BAUDRATE = baudrate;
    }
    public static void PushConnectButton()
    {
        if (IsConnected())
        {
            DisconnectPort();
        }
        else
        {
            ConnectPort();
        }
    }

    public static void ConnectPort()
    {
        if (isConnected) return;

        UpdateSerialPortComboBox();
        string port_name = SerialPortComboBox.Text;
        if (String.IsNullOrEmpty(port_name)) return;
        port = new SerialPort(port_name, BAUDRATE, Parity.None, 8, StopBits.One);
        try
        {
            port.Open();
            port.DtrEnable = true;
            port.RtsEnable = true;
            isConnected = true;
            ConnectButton.Content = "Disconnect";
            Console.WriteLine("Connected.");
            port.DataReceived += new SerialDataReceivedEventHandler(data_received_handle_);
        }
        catch (Exception err)
        {
            Console.WriteLine("Unexpected exception : {0}", err.ToString());
        }
    }
    public static void DisconnectPort()
    {
        if (isConnected)
        {
            port.Close();
            port.Dispose();
            isConnected = false;
            ConnectButton.Content = "Connect";
            Console.WriteLine("Disconnected.");
        }
    }
    public static bool IsConnected()
    {
        return isConnected;
    }

    public static void SetDataReceivedHandle(DataReceivedHandler data_received_handle)
    {
        data_received_handle_ = data_received_handle;
    }

    private static void UpdateSerialPortComboBox()
    {
        // 前に選んでいたポートの取得
        string prev_selected_port = "";
        if (SerialPortComboBox.SelectedItem != null)
            prev_selected_port = SerialPortComboBox.SelectedItem.ToString();

        // ポート一覧の更新
        string[] port_list = SerialPort.GetPortNames();
        SerialPortComboBox.Items.Clear();
        foreach (var i in port_list) SerialPortComboBox.Items.Add(i);

        // 前に選択していたポートを再度選択
        for (int i = 0; i < SerialPortComboBox.Items.Count; i++)
        {
            if (SerialPortComboBox.Items[i].ToString() == prev_selected_port)
                SerialPortComboBox.SelectedIndex = i;
        }
        // ポート数が1以下であれば0番目を選択
        if (SerialPortComboBox.Items.Count <= 1)
            SerialPortComboBox.SelectedIndex = 0;
    }
    private static void SetTimer()
    {
        _timer = new DispatcherTimer();
        _timer.Interval = new TimeSpan(0, 0, 1);
        _timer.Tick += new EventHandler(OnTimedEvent);
        _timer.Start();
        mainWindow.Closing += new CancelEventHandler(StopTimer);
    }
    private static void OnTimedEvent(Object source, EventArgs e)
    {
        UpdateSerialPortComboBox();
    }
    private static void StopTimer(object sender, CancelEventArgs e)
    {
        _timer.Stop();
    }
}
```

# メイン

以下のように書く

```MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.IO.Ports;

namespace SerialPortComboBox
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            COMPortSelector.Init();
            COMPortSelector.SetDataReceivedHandle(aDataReceivedHandler);
        }

        private static void aDataReceivedHandler(
                    object sender,
                    SerialDataReceivedEventArgs e)
        {
            // 受信処理
        }

        private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {

        }

        private void ConnectButton_Click(object sender, RoutedEventArgs e)
        {
            COMPortSelector.PushConnectButton();
        }
    }
}

```

# 解説

COMPortSelector.Init();を呼び出せば機能してくれます。
ボタンが押されるなどに応じてCOMPortSelector.PushConnectButton();を呼び出すことでConnect, Disconnectの切り替えを行います。

以下、COMPortSelectorクラスが行っている処理の解説

SerialPort.GetPortNames()で接続されているポートの一覧を取得することができますが、これをUpdateSerialPortComboBox()内で行い、CmboBoxのアイテム一覧に反映させています。

また、ポートの抜き差しに応じてアイテムの一覧が反映させたいので、DispatcherTimerを使用し、1秒ごとにUpdateSerialPortComboBox()を呼び出すことで実装しています。

他には、SetDataReceivedHandle();を使うと受信イベントによって呼び出すメソッドを設定することができます。

メインで、COMPortSelector.SetDataReceivedHandle(aDataReceivedHandler);とし、
受信ごとにaDataReceivedHandlerが呼び出されます。