20200704のC#に関する記事は4件です。

UWP開発したこともC言語系触ったことないけど3時間で作れるようになってしまった世界。

はじめに

windows10にいつの間にかある切り取り&スケッチ
皆さんは使ったことはありますか?
(win7とかだとSnipping toolしかありませんでしたが)
このツール、画面の切り取りしかだめだと思っていたら、
実はファイルも開けるんですね。
image.png
個人的にはペイント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は初起動です。
起動するとなんか出てきました。
image.png
今回は新しいプロジェクトの作成から、
image.png
黄色いやつを選びます。
(あれ、よく見たらVBでもつくれたんかーい。VBAはやってたから感覚同じだったかもしれない)
その後セットアップを順調に進み、
image.png
こんなのが出ます。

いざ開発

そしたらMainPage.xamlをクリックしデザインを押すとなんか白いスクリーンが出てきます。
そこにツールボックスからボタンとかを設置して、
image.png
ボタンをダブルクリックすると

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.StorageFilefilepickerで取得できるのと同じようなのでそのまま、
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アプリも作ってみたいと思いました。
(まあ多分こんな感じで唐突に必要になるかもだけど。)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Microsoft] 4. サービスを追加 - Angularチュートリアル Tour of Heroes を Blazor で再実装する

サービスを追加

フェイクデータを表示するHeroesコンポーネントを作成しました。
リファクタリングします。


なにゆえにサービスクラスを作成するのか

テストのしやすさから考えると、アプリを構成する部品はそれぞれ関心が分離している方がよいです。
コンポーネントがデータの保存方法について関知しないようにします。

プロジェクトを分けるか分けないか

サービスクラスを作成するにあたり、プロジェクトをどのようにするかが悩ましいです。

今は以下の2つのプロジェクトがあります。

  • BlazorTourOfHeroes WASMプロジェクト
  • BlazorTourOfHeroes.Tests 上記のユニットテストプロジェクト

WASMプロジェクト以外からも使うのであれば、別プロジェクトを用意しないとですね。
今はプロジェクトを分けずに進めます。

HeroServiceを作成する

テスト時にモック化しやすいように、インターフェースとコンクリートクラスを作成します。

Service/IHeroService.cs
namespace BlazorTourOfHeroes.Service
{
    public interface IHeroService
    {
    }
}
Service/HeroService.cs
namespace BlazorTourOfHeroes.Service
{
    public class HeroService : IHeroService
    {
    }
}

ヒーローデータを返すメソッドを作成する

実際には、WebサービスやLocalStorageからデータを返します。
ここではモックです。

IHeroService.cs
List<Hero> GetHeroes();
HeroService.cs
public List<Hero> GetHeroes() => MockHeroes.Create();

DIコンテナへ登録する

コンポーネントヘインジェクトできるように、DIコンテナへ登録します。

DIコンテナへの登録は、Program.csファイルの中で行います。

Program.cs
builder.Services.AddSingleton<IHeroService, HeroService>();

Heroesコンポーネントクラスを更新する

作成したサービスを使うようにコンポーネントを更新します。

インスタンス変数を宣言のみにします。

Heroes.razor
private List<Hero> heroes;

HeroServiceをインジェクトする

DIコンテナへ登録したHeroServiceをコンポーネントで使用できるようにします。

Heroes.razor
@using BlazorTourOfHeroes.Service
@inject IHeroService HeroService

@inject の引数ひとつめが型、ふたつめが変数名です。

GetHeroesメソッドを追加する

サービスからヒーロー達を読み込むメソッドを追加します。

Heroes.razor
private void GetHeroes()
{
    heroes = HeroService.GetHeroes();
}

作成したメソッドをOnInitializedメソッドから呼び出すようにする

コンポーネントの準備が整うと OnInitialized メソッドが呼ばれます。このなかでヒーロー達を読み込むようにします。

Hero.razor
protected override void OnInitialized()
{
    GetHeroes();
}

非同期化

現実世界のアプリでは、ヒーロー達の読み込みがいつ終わるかわかりません。
いつ終わるかわからないものを待つことはできないので、非同期化します。

HeroServiceを非同期にする

GetHeroesメソッドを変更してTaskを返すようにします。

IHeroService.cs
Task<List<Hero>> GetHeroes();
HeroService.cs
public Task<List<Hero>> GetHeroes() =>
    Task.FromResult(MockHeroes.Create());

awaitする

GetHeroesGetHeroesAsyncに書き換えて、awaitするようにします。

Heroes.razor
private async Task GetHeroesAsync()
{
    heroes = await HeroService.GetHeroes();
}

OnInitializedAsyncに書き換える

OnInitializedの非同期版であるOnInitialiedAsyncを使うようにします。

Heroes.razor
protected override async Task OnInitializedAsync()
{
    await GetHeroesAsync();
}

メッセージを表示する

ここでは次のことを行います。

  • Messageコンポーネントを作成し、画面下部にアプリケーションからのメッセージを表示します。
  • MessageServiceを作成します。
  • MessageServiceHeroServiceヘインジェクトします。
  • 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.cs
using System;
using System.Collections.Generic;

namespace BlazorTourOfHeroes.Service
{
    public interface IMessageService
    {
        IEnumerable<string> Messages { get; }
        void Add(string message);
        void Clear();
    }
}
MessageService.cs
using 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.cs
builder.Services.AddSingleton<IMessageService, MessageService>();

HeroServiceヘインジェクトする

コンポーネントではない場合は、コンストラクタインジェクションになります。

HeroService.cs
private readonly IMessageService messageService;

public HeroService(IMessageService messageService)
{
    this.messageService = messageService;
}

HeroServiceからメッセージを送る

GetHeroesメソッドを変更します。

HeroesService.cs
public 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.razor
private void OnSelect(Hero hero)
{
    selectedHero = hero;
    MessageService.Add($"HeroesComponent: Selected hero id={hero.Id}");
}

HeroServiceの状態変化をコンポーネントで受け取る

Heroesコンポーネントからメッセージを追加していますが、Messageコンポーネントに反映されません。
これを解決するには、2つやることがあります。

  • MessageServiceから状態変化を通知する。
  • MessageコンポーネントでMessageServiceの状態変化通知を受け取る。

HeroServiceから状態変化を通知する

状態変化を通知するイベントを定義します。

IMessageService.cs
event Action OnChange;

状態が変化したとき、イベントハンドラを起動するようにします。

MessageService.cs
public 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;
    }
}

こんなんできました

toh.gif

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】OculusQuestのハンドトラッキングで手のひらの向きを取得する方法

デモ

まずはこちらのデモをご覧ください。
GunShot1.gif

手のひらの方向にレーザービームを発射する実装を施しています。

これがひと手間必要だったのでメモします。

バージョン情報

Unity2019.3.10f1
Oculus Integration 1.49

実装手順

今回の本題の手のひらの向きの取得について考えていきます。

指のボーンそれぞれがTransformの情報を保持しているので、
指の根元の関節からtransform.forwardのようにボーンの正面方向取得すればいける!
と思っていましたがダメでした。

この画像を見ればわかりますが、残念ながら各指のボーンというのは
それぞれが扱いやすいように各指の先端に向けて
正のZ軸方向(すなわち正面方向)を指してはいません。
OQHandTip.png

当然、手のひらの方向なども用意されていません。
つまり、人差し指の向きなり手のひらの向きなりを各々で用意する必要があります。


外積

今回は外積を利用して手のひらの向きを計算しました。
下記画像のように親指、小指、中指それぞれの根元のボーンの3点から
外積により手のひらの向きが算出できます。

CrossHand.png

計算をコードに落とし込むと下記です。

右手の場合
//外積に使用 各指の根元
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の座標系は左手座標系となっているそうなので、
フレミングの左手の法則の形を作って親指が第一引数に渡すベクトルとなる、、、
と考えるのが良さそうです。

参考リンク

unity学習帳
フレミングの左手の法則

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンボボックスに表示名と値を入れる

ComboBoxにおいて表示名と値を関連付ける

cmb.cs
using 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();//値はそのまま取りだせる
        }
    }
}

結果

実行すると下記の様に表示される
20200704_Combbox_表示名と値を入れる.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む