C# + WPF でシリアルCOMポートから受信したデータを処理する

引き続き、Microsoft.TeamFoundation.MVVM名前空間を使って作る、WPFアプリの話です。

以前WindowsFormにてシリアルCOMポートから受信したデータを処理したことがありました。
WindowsFormで、シリアルCOMポートから受信したデータを処理する - メモ的な思考的な

そこで、WPFではどのになるのかを試してみました。

 
2015/12/10 追記 ここから

公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVMの使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs

2015/12/10 追記 ここまで

 

環境

 

シリアルCOMポートからの受信

WPFでのSerialPortコントロール相当のものを実装

WindowsFormにはSerialPortコントロールがありましたが、WPFには見当たりませんでした。

そのため、自分でSerialPortクラスを使って実装することになります。
参考: Serial Communication using WPF, RS232 and PIC Communication - CodeProject

といってもWindowsFormとほぼ変わらず、以下のような感じになります。

var serialPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);

// 改行コードまでUIに反映されないよう、システムの改行コードを設定しておく
serialPort.NewLine = Environment.NewLine;

serialPort.Open();
serialPort.DataReceived += (s, e) =>
{
    var readData = serialPort.ReadLine();
    // データ処理
};

serialPort.Close();

 

WPFのUIスレッドに値を渡す

シリアルCOMポートで受け取ったデータをViewに反映させる場合、WPFのUIスレッドに値を渡す必要があります。

Microsoft.TeamFoundation.MVVM名前空間では、DataReceivedイベントの中でMicrosoft.TeamFoundation.MVVM.ViewModelBaseクラスのDispatcherプロパティを使えば、UIスレッドに値を渡すことができます。

なお、WindowsFormの時に使っていたMethodInvokerデリゲートは、名前空間System.Windows.Formsであったため*1、Actionデリゲートを使います。

実際には以下のような感じとなります。

Dispatcher.Invoke(new Action(() =>
{
    if (DataGridSource == null)
    {
        DataGridSource = new ObservableCollection<SerialPortModel>();
    }
    DataGridSource.Add(new SerialPortModel() { ReadData = readData });
}));

 
以上が、WPFでシリアルCOMポートから受信したデータを処理する内容です。

ただ、WPFで画面を作ってみたら他にも色々とやってみたくなったため、いろいろと付け加えてみました。

 

シリアルCOMポートの列挙

手元の環境ではシリアルCOMポートに接続されているため、コンボボックスで使うものを選択するようにしました。

シリアルCOMポートの列挙にはWMIを使えばよいとのことで、

の2つの方法が見つかりましたが、今回は前者を使うことにしました。
参考:

 
そこで、System.Managementを参照に追加し、以下のようなメソッドを用意します。

private ObservableCollection<ComPort> GetComPorts()
{
    var results = new ObservableCollection<ComPort>();

    var mc = new ManagementClass("Win32_SerialPort");
    foreach (var m in mc.GetInstances()) using (m)
        {
            results.Add(new ComPort()
            {
                DeviceID = (string)m.GetPropertyValue("DeviceID"),
                Description = (string)m.GetPropertyValue("Caption")
            });
        }
    return results;
}

 
ここでは、結果をコンボボックスにデータバインディングするため、ObservableCollectionを返しています。

また、GetInstances()メソッドで取得したオブジェクトをforeachで回しますが、そのオブジェクトであるManagementBaseObjectDispolseメソッドを持っているため、usingステートメントを使うようにします。
参考: c# - Combining foreach and using - Stack Overflow

 

コンボボックスへのデータバインディング

以前は入力可能なコンボボックスを使いましたが、今回は選択だけが可能なコンボボックスを使ってみます。

XAMLは基本的な内容になりました。

<ComboBox Grid.Row="1" Margin="10"
          ItemsSource="{Binding Path=ComPorts}"
          DisplayMemberPath="Description"
          SelectedItem="{Binding Path=SelectedComPort}"/>

 
ViewModelは以下のとおり。

private ObservableCollection<ComPort> _comPorts;
public ObservableCollection<ComPort> ComPorts
{
    get
    {
        if (_comPorts == null)
        {
            _comPorts = GetComPorts();
            RaisePropertyChanged();
        }
        return _comPorts;
    }
}

private ComPort _selectedComPort;
public ComPort SelectedComPort
{
    get { return _selectedComPort; }
    set
    {
        _selectedComPort = value;
        RaisePropertyChanged();
    }
}

 
Modelは、こんな感じです。

public class ComPort
{
    public string DeviceID { get; set; }
    public string Description { get; set; }
}

 

DataGridの行番号表示

初めはDataGridの列に行番号を入れようと考えましたが、行を削除した時の動作とかを考えると色々と面倒でした。

そのため、調べてみたところ、以下のstackoverflowのようにビヘイビアを使うのが良さそうでした。
参考: wpfdatagrid - WPF 4 DataGrid: Getting the Row Number into the RowHeader - Stack Overflow

なお、行番号は1始まりがよかったため、GetIndex()メソッドを使っているところを2ヶ所、GetIndex() + 1としました。

 

TextBlock.Backgroundの変更

せっかくなので受信中とかのステータスを出してみたり、ボタンをdisabledにしてみたくなりました。

ボタンのdisabledはRealyCommandの第二引数に渡すやり方にしました。

一方、データバインディングしたBackgroundの変更方法が分からなかったため、以下の記事を参考にBrushを渡して変更するようにしました。
Control.BackgroundプロパティにはColor型を直接バインドできない | be free

StatusBackground = Brushes.LightGray;

 

画面イメージ

起動直後

f:id:thinkAmi:20140911172305p:plain

シリアルCOMポート受信開始

f:id:thinkAmi:20140911172315p:plain

受信したデータの表示

f:id:thinkAmi:20140911172320p:plain

シリアルCOMポート受信終了

f:id:thinkAmi:20140911172324p:plain

ソースコード

GitHubに上げました。
CSharp-Sample/MVVMApp/SerialPortReceiver at master · thinkAmi/CSharp-Sample · GitHub

 

参考

*1:Microsoft.TeamFoundation.MVVMという名前空間を使っているため、あまり名前空間にこだわらなくてもよいのかもしれませんが...