引き続き、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 追記 ここまで
環境
- Windows7
- .NET Framework 4.5
- Microsoft.TeamFoundation.MVVM名前空間を使ったMVVMのWPFアプリ
- 以下のdllを参照設定に追加
System.Management
シリアル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で回しますが、そのオブジェクトであるManagementBaseObject
はDispolse
メソッドを持っているため、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;
画面イメージ
起動直後
シリアルCOMポート受信開始
受信したデータの表示
シリアルCOMポート受信終了
ソースコード
GitHubに上げました。
CSharp-Sample/MVVMApp/SerialPortReceiver at master · thinkAmi/CSharp-Sample · GitHub