- 投稿日:2020-07-04T20:21:58+09:00
UWP開発したこともC言語系触ったことないけど3時間で作れるようになってしまった世界。
はじめに
windows10にいつの間にかある切り取り&スケッチ
皆さんは使ったことはありますか?
(win7とかだとSnipping toolしかありませんでしたが)
このツール、画面の切り取りしかだめだと思っていたら、
実はファイルも開けるんですね。
個人的にはペイント3Dとかだと消しゴムツールで画像を消してしまうので、
使いにくいと思ってたのですが、なんとこいつは、
ストローク消しゴム付きでしかも読み込んだ画像は消さないという、
まさに求めている機能が全部入っているのです。
さてこのアプリ一つ、致命的問題がありまして、
画像をアプリケーションのアイコンにD&Dしても反応しないんです。
意味がわからないと思うんですが、なんか画像をD&Dしても開きません。
普段使っているペイントソフトは大半開いてくれるのですが、こいつは駄目です。
しかしどうしてもD&DしたいのでBATファイルを作ってみるも、
そもそもEXEファイルを選択してもアクセスは拒否され、
URI(ms-screensketch:)を使ってもそもそも画像パスを入れても使えません。そしてたどり着いた先は.....
https://docs.microsoft.com/ja-jp/windows/uwp/launch-resume/launch-screen-snipping
もう意味がわかりません。
sharedAccessTokenって何?
なんで起動のためにC#がいるの??
となりました。そしてsharedAccessTokenを調べていくうちにあることに気づきました。
これUWPからしか使えないんじゃね
(多分そんなことはないと思いますがこのときの私はこれしかわかりませんでした。)結果UWPを作る羽目になりました。
ても作り方は知りません。
何ならC#すら使えません。
やばいですが、できる範囲でがんばります。開発環境
OS:windows10
いつもの奴。今回の主役
VS studio 2019
初めて使う。パソコンもらったときに入ってた。
Clip studio
アイコン作成用。話には出てこない。準備運動
VSCodeはよく使ってますが、VS studioは初起動です。
起動するとなんか出てきました。
今回は新しいプロジェクトの作成から、
黄色いやつを選びます。
(あれ、よく見たらVBでもつくれたんかーい。VBAはやってたから感覚同じだったかもしれない)
その後セットアップを順調に進み、
こんなのが出ます。いざ開発
そしたらMainPage.xamlをクリックしデザインを押すとなんか白いスクリーンが出てきます。
そこにツールボックスからボタンとかを設置して、
ボタンをダブルクリックするとprivate async void Button_Click(object sender, RoutedEventArgs e)と出てきたのでこの中にクリック時のプログラムを書いていきました。
今回はファイル選択を作るために、
https://docs.microsoft.com/ja-jp/windows/uwp/files/quickstart-using-file-and-folder-pickers
を参考にこんな感じに書きましたvar picker = new Windows.Storage.Pickers.FileOpenPicker(); picker.ViewMode = Windows.Storage.Pickers.PickerViewMode.Thumbnail; picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop; picker.FileTypeFilter.Add(".jpg"); picker.FileTypeFilter.Add(".jpeg"); picker.FileTypeFilter.Add(".png"); var files = await picker.PickMultipleFilesAsync(); if (files.Count > 0) { foreach (Windows.Storage.StorageFile file in files) { // Application now has read/write access to the picked file this.textBlock.Text = "Picked photo: " + file.Name; String sharingToken = Windows.ApplicationModel.DataTransfer.SharedStorageAccessManager.AddFile(file); Uri driveTo = new Uri("ms-screensketch:edit?isTemporary=false&sharedAccessToken=" + sharingToken); var launch = await Windows.System.Launcher.LaunchUriAsync(driveTo); } } else { this.textBlock.Text = "Operation cancelled."; }このコードの真ん中ぐらいにあるのが今回の主役で、
どうやらWindows.Storage.StorageFile
はfilepicker
で取得できるのと同じようなのでそのまま、
sharedAccessTokenを取得するコードである、String sharingToken = SharedStorageAccessManager.AddFile(file);が使えました。あとはURIを生成して、
var launch = await Windows.System.Launcher.LaunchUriAsync(driveTo);で実行させるだけです。
これで機能的には出来上がったのですが、
D&Dもしたいので
https://docs.microsoft.com/ja-jp/windows/uwp/design/input/drag-and-drop
を参考に、MainPage.xaml.csにprivate void Grid_DragOver(object sender, DragEventArgs e) { e.AcceptedOperation = DataPackageOperation.Copy; } private async void Grid_Drop(object sender, DragEventArgs e) { if (e.DataView.Contains(Windows.ApplicationModel.DataTransfer.StandardDataFormats.StorageItems)) { var items = await e.DataView.GetStorageItemsAsync(); foreach (Windows.Storage.StorageFile file in items) { String sharingToken = Windows.ApplicationModel.DataTransfer.SharedStorageAccessManager.AddFile(file); Uri driveTo = new Uri("ms-screensketch:edit?isTemporary=false&sharedAccessToken=" + sharingToken); var launch = await Windows.System.Launcher.LaunchUriAsync(driveTo); } } }を書き足しました。
ただ書き足す場所がわからなかったのでとりあえず
public sealed partial class MainPage : Page
に書き足しておきました。あとはAssetsフォルダ内の画像をロゴマークに塗り替えて、ビルドすれば完成です。
ビルド時の注意としては、
証明書を用意しておかないとインストールできないので自己証明書でもいいので作っておきます。
あとはアプリ名(Universal Windows)を右クリックし、
公開>アプリパッケージの作成を押して、指示に従ってビルドするだけです。
お疲れさまでした。まとめ
なんか難しそうなイメージがあったWindowsアプリの作成も意外と簡単にできて驚きました。
ただ一つ心残りがあるとすれば、初回起動時にWindowサイズが指定したのと違うってことぐらいですかね。
これを機にAndroidアプリも作ってみたいと思いました。
(まあ多分こんな感じで唐突に必要になるかもだけど。)
- 投稿日:2020-07-04T19:58:27+09:00
[Microsoft] 4. サービスを追加 - Angularチュートリアル Tour of Heroes を Blazor で再実装する
サービスを追加
フェイクデータを表示するHeroesコンポーネントを作成しました。
リファクタリングします。
なにゆえにサービスクラスを作成するのか
テストのしやすさから考えると、アプリを構成する部品はそれぞれ関心が分離している方がよいです。
コンポーネントがデータの保存方法について関知しないようにします。プロジェクトを分けるか分けないか
サービスクラスを作成するにあたり、プロジェクトをどのようにするかが悩ましいです。
今は以下の2つのプロジェクトがあります。
- BlazorTourOfHeroes WASMプロジェクト
- BlazorTourOfHeroes.Tests 上記のユニットテストプロジェクト
WASMプロジェクト以外からも使うのであれば、別プロジェクトを用意しないとですね。
今はプロジェクトを分けずに進めます。
HeroService
を作成するテスト時にモック化しやすいように、インターフェースとコンクリートクラスを作成します。
Service/IHeroService.csnamespace BlazorTourOfHeroes.Service { public interface IHeroService { } }Service/HeroService.csnamespace BlazorTourOfHeroes.Service { public class HeroService : IHeroService { } }ヒーローデータを返すメソッドを作成する
実際には、WebサービスやLocalStorageからデータを返します。
ここではモックです。IHeroService.csList<Hero> GetHeroes();HeroService.cspublic List<Hero> GetHeroes() => MockHeroes.Create();DIコンテナへ登録する
コンポーネントヘインジェクトできるように、DIコンテナへ登録します。
DIコンテナへの登録は、
Program.cs
ファイルの中で行います。Program.csbuilder.Services.AddSingleton<IHeroService, HeroService>();Heroesコンポーネントクラスを更新する
作成したサービスを使うようにコンポーネントを更新します。
インスタンス変数を宣言のみにします。
Heroes.razorprivate List<Hero> heroes;
HeroService
をインジェクトするDIコンテナへ登録した
HeroService
をコンポーネントで使用できるようにします。Heroes.razor@using BlazorTourOfHeroes.Service @inject IHeroService HeroService
@inject
の引数ひとつめが型、ふたつめが変数名です。
GetHeroes
メソッドを追加するサービスからヒーロー達を読み込むメソッドを追加します。
Heroes.razorprivate void GetHeroes() { heroes = HeroService.GetHeroes(); }作成したメソッドを
OnInitialized
メソッドから呼び出すようにするコンポーネントの準備が整うと OnInitialized メソッドが呼ばれます。このなかでヒーロー達を読み込むようにします。
Hero.razorprotected override void OnInitialized() { GetHeroes(); }非同期化
現実世界のアプリでは、ヒーロー達の読み込みがいつ終わるかわかりません。
いつ終わるかわからないものを待つことはできないので、非同期化します。
HeroService
を非同期にする
GetHeroes
メソッドを変更してTask
を返すようにします。IHeroService.csTask<List<Hero>> GetHeroes();HeroService.cspublic Task<List<Hero>> GetHeroes() => Task.FromResult(MockHeroes.Create());
await
する
GetHeroes
をGetHeroesAsync
に書き換えて、await
するようにします。Heroes.razorprivate async Task GetHeroesAsync() { heroes = await HeroService.GetHeroes(); }
OnInitializedAsync
に書き換える
OnInitialized
の非同期版であるOnInitialiedAsync
を使うようにします。Heroes.razorprotected override async Task OnInitializedAsync() { await GetHeroesAsync(); }メッセージを表示する
ここでは次のことを行います。
- Messageコンポーネントを作成し、画面下部にアプリケーションからのメッセージを表示します。
MessageService
を作成します。MessageService
をHeroService
ヘインジェクトします。HeroService
がヒーロ達を読み込んだら、メッセージを表示するようにします。Messageコンポーネントを作成する
dotnetコマンドを使ってMessageコンポーネントを作成します。
dotnet new razorcomponent -o BlazorTourOfHeroes/Shared -n Message
Indexページを編集して、Messageコンポーネントを表示するようにします。
Index.razor<h1>Tour of Heroes</h1> <Heroes></Heroes> <Message></Message>
MessageService
を作成するインターフェースとコンクリートクラスを作成します。
IMessageService.csusing System; using System.Collections.Generic; namespace BlazorTourOfHeroes.Service { public interface IMessageService { IEnumerable<string> Messages { get; } void Add(string message); void Clear(); } }MessageService.csusing System; using System.Collections.Generic; namespace BlazorTourOfHeroes.Service { public class MessageService : IMessageService { private readonly List<string> messages = new List<string>(); public IEnumerable<string> Messages { get { return messages; } } public void Add(string message) { messages.Add(message); } public void Clear() { messages.Clear(); } } }
MessageService
をDIコンテナへ登録するDIコンテナへ登録します。
Program.csbuilder.Services.AddSingleton<IMessageService, MessageService>();
HeroService
ヘインジェクトするコンポーネントではない場合は、コンストラクタインジェクションになります。
HeroService.csprivate readonly IMessageService messageService; public HeroService(IMessageService messageService) { this.messageService = messageService; }
HeroService
からメッセージを送る
GetHeroes
メソッドを変更します。HeroesService.cspublic Task<List<Hero>> GetHeroes() { // TODO: ヒーロー達を取得した __後で__ メッセージを送るようにする messageService.Add("HeroService: fetched heroes"); return Task.FromResult(MockHeroes.Create()); }
HeroService
からのメッセージを表示するMessageコンポーネントを変更してメッセージを表示するようにします。
OnInitialized
の中で、MessageService
の状態変化を受け取るハンドラStateHasChanged
を登録しています。
登録を解除できるように、IDisposable
を実装しています。Message.razor@using BlazorTourOfHeroes.Service @inject IMessageService MessageService @if (MessageService.Messages.Count() > 0) { <h2>Messages</h2> <button class="clear" @onclick="MessageService.Clear">Clear</button> @foreach (var message in MessageService.Messages) { <div>@message</div> } }Heroコンポーネントにメッセージを追加する
ヒーロー選択時に、メッセージを追加するようにします。
Heroes.razorprivate void OnSelect(Hero hero) { selectedHero = hero; MessageService.Add($"HeroesComponent: Selected hero id={hero.Id}"); }
HeroService
の状態変化をコンポーネントで受け取るHeroesコンポーネントからメッセージを追加していますが、Messageコンポーネントに反映されません。
これを解決するには、2つやることがあります。
MessageService
から状態変化を通知する。- Messageコンポーネントで
MessageService
の状態変化通知を受け取る。
HeroService
から状態変化を通知する状態変化を通知するイベントを定義します。
IMessageService.csevent Action OnChange;状態が変化したとき、イベントハンドラを起動するようにします。
MessageService.cspublic event Action OnChange; public void Add(string message) { messages.Add(message); NotifyChange(); } public void Clear() { messages.Clear(); NotifyChange(); } // ハンドラが登録されていれば変更を通知 private void NotifyChange() => OnChange?.Invoke();Messageコンポーネントで
MessageService
の状態変化通知を受け取る
OnInitialized
のなかで、StateHasChanged
ハンドラを登録します。
コンポーネント廃棄時にハンドラを登録解除するため、IDisposable
を実装します。
Dispose
のなかで登録解除します。Message.razor@using BlazorTourOfHeroes.Service @implements IDisposable @inject IMessageService MessageService @if (MessageService.Messages.Count() > 0) { <h2>Messages</h2> <button class="clear" @onclick="MessageService.Clear">Clear</button> @foreach (var message in MessageService.Messages) { <div>@message</div> } } @code { protected override void OnInitialized() { MessageService.OnChange += StateHasChanged; } public void Dispose() { MessageService.OnChange -= StateHasChanged; } }こんなんできました
- 投稿日:2020-07-04T16:41:54+09:00
【Unity(C#)】OculusQuestのハンドトラッキングで手のひらの向きを取得する方法
デモ
手のひらの方向にレーザービームを発射する実装を施しています。
これがひと手間必要だったのでメモします。
バージョン情報
Unity2019.3.10f1
Oculus Integration 1.49実装手順
今回の本題の手のひらの向きの取得について考えていきます。
指のボーンそれぞれがTransformの情報を保持しているので、
指の根元の関節からtransform.forward
のようにボーンの正面方向取得すればいける!
と思っていましたがダメでした。この画像を見ればわかりますが、残念ながら各指のボーンというのは
それぞれが扱いやすいように各指の先端に向けて
正のZ軸方向(すなわち正面方向)を指してはいません。
当然、手のひらの方向なども用意されていません。
つまり、人差し指の向きなり手のひらの向きなりを各々で用意する必要があります。
外積
今回は外積を利用して手のひらの向きを計算しました。
下記画像のように親指、小指、中指それぞれの根元のボーンの3点から
外積により手のひらの向きが算出できます。計算をコードに落とし込むと下記です。
右手の場合//外積に使用 各指の根元 private OVRSkeleton.BoneId _middleFingerRoot = OVRSkeleton.BoneId.Hand_Middle1; private OVRSkeleton.BoneId _thumbFingerRoot = OVRSkeleton.BoneId.Hand_Thumb0; private OVRSkeleton.BoneId _pinkyFingerRoot = OVRSkeleton.BoneId.Hand_Pinky0; //外積に利用するベクトル Vector3 pinkyToMiddleDirection = transform.TransformPoint(_ovrSkeletonR.Bones[(int) _pinkyFingerRoot].Transform.position) - transform.TransformPoint(_ovrSkeletonR.Bones[(int) _middleFingerRoot].Transform.position); Vector3 thumbToMiddleDirection = transform.TransformPoint(_ovrSkeletonR.Bones[(int) _thumbFingerRoot].Transform.position) - transform.TransformPoint(_ovrSkeletonR.Bones[(int) _middleFingerRoot].Transform.position); //外積 手のひら正面方向 中指の根本を手のひらってことにする Vector3 handForward = Vector3.Cross(thumbToMiddleDirection, pinkyToMiddleDirection).normalized;あとは算出した手のひら正面方向に対して、
適当な変数を用意して位置調整すれば発射位置を変更できます。[SerializeField, Range(0.1f, 1f)] private float _controllerPositionAdjuster = 0.2f; handForward += handForward * _controllerPositionAdjuster;最後に
左右でVector3.Crossの引数に渡すベクトルの順番を変更しなければ
手のひらの向きと反対の方向のベクトルを計算してしまうので注意が必要です。Unityの座標系は左手座標系となっているそうなので、
フレミングの左手の法則の形を作って親指が第一引数に渡すベクトルとなる、、、
と考えるのが良さそうです。参考リンク
- 投稿日:2020-07-04T02:59:30+09:00
コンボボックスに表示名と値を入れる
例
ComboBoxにおいて表示名と値を関連付ける
cmb.csusing System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace ComboBoxDispalyValue { public partial class Form1 : Form { public class ItemSet{ // DisplayMemberとValueMemberにはプロパティで指定する仕組み public String ItemDisp { get; set; } public int ItemValue { get; set; } // プロパティをコンストラクタでセット public ItemSet(int v, String s){ ItemDisp = s; ItemValue = v; } } public Form1() { InitializeComponent(); // ComboBox用データ作成 //ListでOK //IList インターフェイスまたは IListSource インターフェイスを実装する、DataSet または Array などのオブジェクト。 List<ItemSet> src = new List<ItemSet>(); src.Add(new ItemSet(100, "Number1"));/// 1つでItem1つ分となる src.Add(new ItemSet(200, "Number2")); src.Add(new ItemSet(300, "Number3")); // ComboBoxに表示と値をセット comboBox1.DataSource = src; comboBox1.DisplayMember = "ItemDisp"; comboBox1.ValueMember = "ItemValue"; // 初期値セット comboBox1.SelectedIndex = 0; comboBox1_SelectedIndexChanged(null, null); } private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { // labelに現在コンボ選択の内容を表示 ItemSet tmp = ((ItemSet)comboBox1.SelectedItem);//表示名はキャストして取りだす labelDisplay.Text = tmp.ItemDisp; labelValue.Text = comboBox1.SelectedValue.ToString();//値はそのまま取りだせる } } }結果