- 投稿日:2019-05-23T21:32:57+09:00
[WPF]DataTriggerを使って、ViewModelのフラグ等にバインドして、見た目を変化させる
やりたいこと
ビューモデルのフラグなどにバインドして、画面の見た目を変えたい。
やり方
DataTrigger
というのを使って実現できた。Styleの中にDataTriggerを組み込んでやる感じ。
似たようなものに、ただのTrigger
があるが、こちらはそのコントロール自身のプロパティを見て、自身のほかのプロパティを切り替えるようなことができるっぽい。下記に、その両方のサンプルを載せる。
View(xaml)
UserControl1.xaml<UserControl x:Class="PrismSample.Views.UserControl1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PrismSample.Views" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid Background="Beige"> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <!-- DataTrigger --> <Ellipse Grid.Column="1" StrokeThickness="5" Stroke="Black"> <Ellipse.Style> <Style TargetType="Ellipse"> <Style.Triggers> <DataTrigger Binding="{Binding ColorChangeFlag}" Value="true"> <Setter Property="Fill" Value="Blue"/> </DataTrigger> </Style.Triggers> </Style> </Ellipse.Style> </Ellipse> <!-- PropertyTrigger --> <Ellipse Grid.Column="0" StrokeThickness="5" Stroke="Black"> <Ellipse.Style> <Style TargetType="Ellipse"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="Fill" Value="Red"/> </Trigger> </Style.Triggers> </Style> </Ellipse.Style> </Ellipse> <!-- ボタン --> <Button Grid.Row="1" Grid.ColumnSpan="2" Content="ボタン" Command="{Binding ButtonCommand}"/> </Grid> </UserControl※Prismを使っているので、画面はUserControlで作る。
ViewModel
UserControl1ViewModel.csusing Microsoft.Practices.Unity; using Prism.Commands; using Prism.Mvvm; using Prism.Regions; namespace PrismSample.ViewModels { class UserControl1ViewModel : BindableBase, INavigationAware { // ボタン押下時処理 public DelegateCommand ButtonCommand { get; } // ★〇の色を切り替えるためのフラグ private bool _colorChangeFlag = false; public bool ColorChangeFlag { get { return _colorChangeFlag; } set { SetProperty(ref _colorChangeFlag, value); } } // コンストラクタ public UserControl1ViewModel() { this.ButtonCommand = new DelegateCommand(() => { // ★ボタンをおしたら、フラグが切り替わる ColorChangeFlag = !ColorChangeFlag; }); } // --------- Prismお決まり部分 ------------- [Dependency] public IRegionManager RegionManager { get; set; } public bool IsNavigationTarget(NavigationContext navigationContext) => false; public void OnNavigatedFrom(NavigationContext navigationContext) { } public void OnNavigatedTo(NavigationContext navigationContext) { } } }できあがり
■DataTriggerについて
ボタンを押すと、DataTriggerにバインドしているViewModelのフラグが切り替わって、画面に反映され、右側の円が青くなる。■PropertyTriggerについて
Ellipseのコントロール自身が持っている、マウスが上に来たらtrueになるフラグIsMouseOver
がtrueになったら、自分の背景色のプロパティFill
をRedにする。参考
DataTriggerの使い方(WPF)
http://memeplex.blog.shinobi.jp/wpf/datatrigger%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9%EF%BC%88wpf%EF%BC%89コード
https://github.com/tera1707/WPF-/tree/master/027_DataTrigger
- 投稿日:2019-05-23T19:18:00+09:00
C#で関数電卓のようなものを作ってみた
最終更新:2019/5/25
ver.1.1.2プログラム言語をつくってみたいが、なかなかハードルが高い・・・
最初の一歩として関数電卓(のようなもの)をつくってみよう!
とういわけで作ってみました
ソースコードに関しては、Calculatorクラス, Mainメソッドまで飛ばしますようお願いします
内容
今回の電卓は、 逆ポーランド記法で書く
スペースで区切って入力する起動時の画面はこんな感じ
(演算子,命令の説明は今後変更予定)
> 5 2 + 3 4 * 5 - +
は
(5 + 2) + ((3 * 4) - 5)
の意味であるまた、変数機能が使用でき、
var:変数名
で変数を定義する
(初期値は0.0)そして、
in:変数名
で一番後ろの数値を変数に入れる
(変数が変数が定義されていない場合は、自動的に定義される)
out:変数名
もしくは
変数名
で値を数値Stackに入れる処理
Dictionary<string, 演算>に、演算子をkey、演算をvalueに登録する
(以下、演算Dictionary)
またStack<double>に、数値を登録する
(以下、数値Stack)まず、入力されたstringが演算Dictionaryに登録しているかどうか調べる
登録してあるなら、演算Dictionaryの演算が求めている数の数字を数値Stackから演算に与える
そして、その返り値を数値Stackに追加する登録してない場合、doubleに変換できるなら数値Stackに追加する
それを最後まで繰り返す
今後
- 演算子の充実
- 中置記法で書けるようにする
Calculatorクラス
以下が「計算機」のソースコード
Calculator.csusing System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Calculator { // 計算機 class Calculator { // 演算子のディクショナリ static public Dictionary<string, Func<double, double>> operatorsOf1 = new Dictionary<string, Func<double, double>>() { ["%"] = (a) => a * 0.01, ["-%"] = (a) => a * 100.0, ["++"] = (a) => a + 1.0, ["--"] = (a) => a - 1.0, ["sq"] = (a) => a * a, ["sqrt"] = (a) => Math.Sqrt(a), ["bx"] = (a) => a * a * a, ["bxrt"] = (a) => Math.Pow(a, 1.0 / 3.0), }; static public Dictionary<string, Func<double, double, double>> operatorsOf2 = new Dictionary<string, Func<double, double, double>>() { ["+"] = (a, b) => a + b, ["-"] = (a, b) => a - b, ["*"] = (a, b) => a * b, ["/"] = (a, b) => a / b, ["mod"] = (a, b) => a % b, ["div"] = (a, b) => (a - (a % b)) / b, ["^"] = (a, b) => Math.Pow(a, b), }; static public Dictionary<string, Func<double[], double>> operatorsAll = new Dictionary<string, Func<double[], double>>() { ["cnt"] = (a) => a.Count(), ["sum"] = (a) => a.Sum(), ["ave"] = (a) => a.Average(), }; // 命令のディクショナリ static public Dictionary<string, Action<Calculator, string>> instructions = new Dictionary<string, Action<Calculator, string>>() { ["clr"] = (calc, _) => { calc.numStack.Clear(); }, ["help"] = (calc, _) => { Console.WriteLine("1項演算子"); Prints(operatorsOf1.Select(x => x.Key)); Console.WriteLine("2項演算子"); Prints(operatorsOf2.Select(x => x.Key)); Console.WriteLine("全てへの演算子"); Prints(operatorsAll.Select(x => x.Key)); Console.WriteLine("命令"); Prints(instructions.Select(x => x.Key)); }, ["var"] = (calc, name) => { if (name != null && !calc.variables.ContainsKey(name)) { calc.variables[name] = 0.0; } }, ["in"] = (calc, name) => { if (calc.numStack.Count >= 1) { calc.variables[name] = calc.numStack.Pop(); } }, ["out"] = (calc, name) => { if (calc.variables.ContainsKey(name)) { calc.numStack.Push(calc.variables[name]); } }, ["vars"] = (calc, _) => { foreach (var item in calc.variables) { Console.WriteLine("{0,6} = {1}", item.Key, item.Value); } Console.WriteLine(); }, }; // 演算子,命令についての説明 static public Dictionary<string, string> comments = new Dictionary<string, string>() { ["%"] = "前の数値を、パーセント数値にする(*100", ["-%"] = "前のパーセント数値を、数値にする(*0.01", ["++"] = "前の数字を、+1する", ["--"] = "前の数字を、-1する", ["sq"] = "前の数字を、平方する", ["sqrt"] = "前の数字を、平方根にする", ["bx"] = "前の数字を、立方する", ["bxrt"] = "前の数字を、立方根にする", ["+"] = "前の数字2つを、足し算する", ["-"] = "前の数字2つを、引き算する", ["*"] = "前の数字2つを、掛け算する", ["/"] = "前の数字2つを、割り算する", ["mod"] = "前の数字2つを、割り算し、その余りをとる", ["div"] = "前の数字2つを、割り算し、その商をとる", ["^"] = "前の数字2つを、累乗する", ["cnt"] = "前の数字全てを、計数する", ["sum"] = "前の数字全てを、合計する", ["ave"] = "前の数字全てを、平均する", ["clr"] = "数値スタックを、全て消去する", ["help"] = "演算子,命令の一覧,説明を、出力する", ["var"]="変数を登録する", ["in"]= "前の数字を、変数に入れる", ["out"]="変数を数値Stackに入れる", ["vars"]="全ての変数とその値を出力する", }; // 数値を入れておくスタック public Stack<double> numStack = new Stack<double>(); // 変数のディクショナリ public Dictionary<string, double> variables = new Dictionary<string, double>(); // 計算もしくは数字を追加する public bool PassString(string txt) { var txts = txt.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (numStack.Count >= 1 && operatorsOf1.ContainsKey(txt)) { var num = numStack.Pop(); numStack.Push(operatorsOf1[txt](num)); return true; } else if (numStack.Count >= 2 && operatorsOf2.ContainsKey(txt)) { var num1 = numStack.Pop(); var num2 = numStack.Pop(); numStack.Push(operatorsOf2[txt](num2, num1)); return true; } else if (operatorsAll.ContainsKey(txt)) { var nums = numStack.ToArray(); numStack.Clear(); numStack.Push(operatorsAll[txt](nums)); return true; } else if (txts.Length >= 1 && instructions.ContainsKey(txts[0])) { instructions[txts[0]](this, txts.Length >= 2 ? txts[1] : null); return true; } else if (double.TryParse(txt, out var num)) { numStack.Push(num); return true; } else if (variables.ContainsKey(txt)) { numStack.Push(variables[txt]); return true; } return false; } // txtsを順に計算もしくは数字を追加する public double[] PassStrings(string[] txts, bool debug = false) { foreach (var txt in txts) { PassString(txt); if (debug) { Console.Write("= "); if (numStack.Count > 0) { foreach (var num in numStack.Reverse()) { Console.Write("{0}, ", num); } Console.Write('\n'); } else { Console.WriteLine("nothing"); } } } if (debug) { Console.WriteLine(); } return numStack.Reverse() .ToArray(); } // txtの命令を実行する public bool Instruction(string txt) { var txts = txt.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); if (instructions.ContainsKey(txts[0])) { instructions[txts[0]](this, txts.Length == 1 ? null : txts[1]); return true; } return false; } static void Prints(IEnumerable<string> a) { foreach (var item in a) { Console.WriteLine("{0,6} = {1}", item, comments.ContainsKey(item) ? comments[item] : "not found"); } Console.WriteLine(); } } }Mainメソッド
Program.csusing System; namespace Calculator { class Program { static void Main(string[] args) { // 計算機 var calc = new Calculator(); Console.WriteLine("Scientific Calculator"); Console.WriteLine("var.1.0.0"); Console.WriteLine(); Console.WriteLine("演算子,命令の一覧,説明は、'help'で確認できます"); Console.WriteLine(); //下のwhileを繰り返すかどうか var isRepeat = true; while (isRepeat) { // コマンドから入力したものを' '(空白)で分割する Console.Write("> "); var inputs = Console.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); Console.WriteLine(); // 分割した入力配列を渡す var nums = calc.PassStrings(inputs, debug: false); Console.Write("= "); if (nums.Length > 0) { foreach (var num in nums) { Console.Write("{0}, ", num); } Console.Write('\n'); } else { Console.WriteLine("nothing"); } Console.WriteLine(); } } } }
- 投稿日:2019-05-23T02:33:34+09:00
Jetson nano + .NET Core 3.0 でLチカを行う
はじめに
Microsoftより今後の.NETは.NET Coreに統合されていくことがアナウンスされました。
WindowsでもLinuxでもx86でもARM64でも動作するプラットフォームは大変強力です。クロスプラットフォームといえばPythonやJavaScriptがありますが、
動作速度が早いことや開発のしやすさ、過去の資産が豊富などC#には別の魅力があります。今回はJetson nano に .NET Core 3.0 を導入してI/Oの制御を行ってみたいと思います。
.Net Core 3.0実行環境の準備
インストール
githubのissue とか gist に情報があります。
以下のコマンドでインストールできます。#依存パッケージのインストール sudo apt update sudo apt upgrade sudo apt -y install libc6 libgcc1 libgssapi-krb5-2 libicu60 liblttng-ust0 libssl1.0.0 libstdc++6 zlib1g #.NetCore本体のインストール(最新版が入る) curl -SL -o dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/master/dotnet-sdk-latest-linux-arm64.tar.gz sudo mkdir -p /usr/share/dotnet sudo tar -zxf dotnet.tar.gz -C /usr/share/dotnet sudo ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet #インストールしたバージョンを確認 dotnet --list-sdks先ほどのgithubのissueを見てみると
Initial support for ARM64 was added in the .NET Core 2.1 release. The team is not maintaining .NET Core 2.x branches with respect to ARM64. All ARM64 improvements will be made in the .NET Core 3.0 branch (currently master).
となっているので、ARM64勢は最新版の.NET Core 3.0を使うのが良さそうです。
とは言っても上のコマンドで入るのはPreview版です。利用は自己責任ですね。(2019.5.23時点).NET Core 3.0は強力です。(新機能の説明)
C#8.0対応なのも嬉しいですし、LinuxでSerialPortが使えるようになりました。
Jetson nanoとは関係ないですが、Raspberry PiのGPIOをサポートしてたり、IoT向けの用途を意識しています。ちなみに、Raspberry Piにおいては前提になるパッケージは少し異なります。
(コマンド:sudo apt-get install curl libunwind8 gettext apt-transport-https
)
本体インストールのコマンドはファイル名の"arm64"のところを"arm"に変更してインストールできます。動作確認
コンソールアプリケーションを作成して実行してみます
dotnet new console -o app cd app dotnet runJITコンパイラが動作するため起動が若干もたつきますが、
「Hello World!」と表示されれば成功です。開発環境について
ここはぜひともVSCodeを使いたいところです。
がしかし、[WARNING]: Processor architecture 'aarch64' is not currently supported by the .NET Core debugger. Debugging will not be available.
悲しみ。
サポートされるまでは根性でデバッグするしか無いようです。
ハードウェア依存部を切り離して大半をWindows環境で開発するなどデバッグ負担を減らす工夫が必要ですね。
複雑なことをやろうとするとアレですが、まぁ、IoT機器として使うレベルの単純なハードウェア制御ならなんとかなるんじゃないでしょうか。ちなみにですが、最近insiderリリースされたVSCodeのリモート開発機能もx86/x64しかサポートしていないのでJetsonやRPiでは使えません。
IO制御方法について
IO制御方法はパッと思いつく方法は以下の2パターンです。
- CPUのIO制御レジスタのアドレスを調べて直接アクセスする
- Raspberry Piと同様に仮想ファイルでIOを制御する
このうち前者はCPUのハードウェアマニュアルが無料公開されてい様子。断念します。
仮想ファイルによるIO制御方法の確認
以下のディレクトリ
/sys/class/gpio/
にGPIOの仮想ファイルを作成してアクセスします。
最初は、仮想ファイルは無く、exportなどのディレクトだけがある状態です。$ ls /sys/class/gpio/ export gpiochip0 unexportexportにGPIOのピン番号を書き込むと、仮想ファイルが生成されます。
GPIO12に出力する場合の例は以下の通り(実行時には、管理者権限が必要になる)$ sudo echo 12 > /sys/class/gpio/export # GPIO12の仮想ファイルを生成 $ sudo echo out > /sys/class/gpio/gpio12/direction # GPIO12を出力に設定 $ sudo echo 0 > /sys/class/gpio/gpio12/value # GPIO12に0出力(Lo) $ sudo echo 1 > /sys/class/gpio/gpio12/value # GPIO12に1出力(Hi) $ sudo echo 12 > /sys/class/gpio/unexport # GPIO12の仮想ファイルを破棄GPIO番号が異なっている以外は Raspberry Piと同じですね。
C#によるIO制御
.NET Core 3.0ではRaspberry PiのGPIO制御がサポートされているらしいですが、GPIO番号が異なるためおそらく使えない(未検証 2019.5.23時点)ので、今回は別の方法で行います。
Raspberry Piと一緒ということは、上記のコマンドライン操作を
丸々C#で模擬することで制御できそうです。
軽く調べてみると、やはり先達がいらっしゃいます
【C#】.NET Core 2.0でRaspberryPi 3のGPIOを操作してLチカ!上記の実装を参考にGPIOクラスを軽く作ります。
詳細な実装はgithubの方を参照ください。
ポイントになる部分のみ抜き出して解説します。ポートの作成(コンストラクタ)
ポートの作成はコンストラクタの処理の中で実施します
public GPIO(int ioNum, Direction d = Direction.In, State s = State.Lo, bool forceCreate = false, string name = "") { string DirPath = "/sys/class/gpio/gpio" + ioNum.ToString(); if (Directory.Exists(DirPath)) { /*ポートが存在していた時の処理(省略)*/ } //exportに番号を書き込んで仮想ディレクトリを作成する File.WriteAllText("/sys/class/gpio/export", ioNum.ToString()); }信号方向の設定
GPIOフォルダの中のdirectionに信号の方向を設定します
public void SetDirection(Direction d) { //Direction型は自分で定義した列挙型 switch(d) { case Direction.In: File.WriteAllText(DirPath + "/direction", "in"); break; case Direction.Out: File.WriteAllText(DirPath + "/direction", "out"); break; } }I/Oの端子状態を設定
端子状態を設定します
public void SetPinState(State s) { //State型は自分で定義した列挙型 switch(s) { case State.Hi: File.WriteAllText(DirPath + "/value", "1"); break; case State.Lo: File.WriteAllText(DirPath + "/value", "0"); break; } }ここまででLチカくらいなら可能になります。
処理を色々追加すればクラスが完成です。
→実装例動作確認
以下のコードで正しく動いているか確認します。
static void Main(string[] args) { //GPIOインスタンスを作成(=ポートを開く) GPIO testPin = new GPIO(12, GPIO.Direction.Out, GPIO.State.Lo, true,"test"); //Lチカのためのタスクを作成 var task = Task.Run(async () => { //とりあえず10回ON/OFF foreach(var i in Enumerable.Range(1,50)) { testPin.ToggleState(); //端子状態を反転する await Task.Delay(200); //指定時間のwait } }); //タスクを実行、完了まで待機 task.Wait(); }最後に
次はラッパークラスを作って既存のLinuxライブラリ、できればCUDAやNEONを扱うようなものをC#から扱いたいと思います。(いつやるかは未定)
- 投稿日:2019-05-23T01:15:21+09:00
HoloLensアプリ開発手順 備忘録 (ボタン機能(Compound Button、Interaction Receiverなど))
はじめに
すでに沢山の方が開発手順の記事を記載してますが、個人の知識整理として投稿します。
HoloLens用アプリにおけるボタン追加、ボタン押下による処理について記載しています。
※2019/05/22時点の記事になります。1.シーンにHolographic Buttonを追加する
2.InteractionReceiverを継承したスクリプトをアタッチしたオブジェクトを追加する
3.イベント処理を追加する環境
Unity:2017.4.27f1 Personal(64bit)
MRTK:HoloToolkit-Unity-2017.4.3.0-Refresh.unitypackageシーンにHolographic Buttonを追加する
ProjectウインドウのHoloToolkit内のHolographicButtonをHierarchyウインドウに追加する。
※HolographicButtonは HoloToolkit/UX/Prefab/Buttons/の配下にある。HolographicButtonにはCompound Buttonというスクリプトがアタッチしている。このCompound Buttonがボタンそのものを表すスクリプトで、このスクリプトによってボタンの状態がイベントとして送信される仕組みとのこと。Cubeなどのオブジェクトにこのスクリプトをアタッチすることで、色々なオブジェクトを「ボタン」として扱うことができる。
※Compound Buttonスクリプトは HoloToolkit/UX/Scripts/Buttons/の配下にある。InteractionReceiverを使用してボタン押下イベントを処理する
HolographicButtonから送信されるイベントを処理するには、InteractionReceiverを継承したスクリプトをアタッチしたオブジェクトを作成する。
1.Hierarchyウインドウに空のGameObject(※)を追加する。
2.追加した空のGameObjectを選択し、Inspectorウインドウで「Add Component」で、新しいスクリプトを追加する。
3.追加したスクリプトを編集する。using UnityEngine; using HoloToolkit.Unity.Receivers; public class Receiver : InteractionReceiver { // InteractionReceiverを継承させる }4.InteractionReceiverを追加することで、UnityのInspectorウインドウのスクリプトに「Interactables」という項目が追加される。
ここにはInteractionReceiverで受け取るイベントの送信元GameObjectを設定する。デフォルトではSize(送信元GameObjectの個数) : 0 となっている。この値を変更することでElement 0 ~ n が追加で表示されるようになる。このElementにHierarchyウインドウから送信元GameObjectをドラッグアンドドロップすることで設定する。5.イベント発生時の処理を追加する。イベント発生時の処理は以下の様にinteractionReceiverで追加される各種メソッドをオーバーライドすることで実装する。
public class Receiver : InteractionReceiver { // メソッドをオーバーライドして処理を追加する protected override void InputClicked(GameObject obj, InputEventData eventData) { base.InputClicked(obj, eventData); // どのオブジェクトからのイベントかはobj.nameで判断できる switch (obj.name) ... } }今回はボタンのクリックイベントを使用したいので、InputClicked()メソッドをオーバーライドする(他にもFocusEnter()など様々なイベントが用意されている)。どのオブジェクトからのイベントかは引数objのフィールドobj.nameを参照することで判別できる。
使用例
今回はボタンを押下すると、キューブの色と、3Dテキストの文字が変わるサンプルを作成して動作を確認。
※キューブと3Dテキストを操作するためにプロパティとしてCube、DisplayMessageを持たせています。
using UnityEngine; using HoloToolkit.Unity.Receivers; using HoloToolkit.Unity.InputModule; public class Receiver : InteractionReceiver { public GameObject Cube; private Renderer cubeRenderer; public GameObject DisplayMessage; private TextMesh dispMsgText; private bool flg = false; // Use this for initialization void Start() { cubeRenderer = Cube.GetComponent<Renderer>(); dispMsgText = DisplayMessage.GetComponentInChildren<TextMesh>(); } protected override void InputClicked(GameObject obj, InputClickedEventData eventData) { base.InputClicked(obj, eventData); switch (obj.name) { case "ButtonColorChange": // フラグ(true/false)の値でキューブの色、3Dテキストを赤・緑で切り替える if (flg) { cubeRenderer.material.color = Color.green; dispMsgText.text = "Green!"; flg = false; } else { cubeRenderer.material.color = Color.red; dispMsgText.text = "Red!"; flg = true; } break; default: break; } } }実行結果
ボタン押下でキューブの色と、3Dテキストが交互に切り替わった。