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

Prism.Wpf 7.2 Regionへの初期表示指定と画面遷移をしてみる

WPFの初心者です。WPFのなんたるやも知らないまま、Prism & ReactivePropertyで MVVM に挑んでいるところですが・・・全然わかりません。まずはPrismのHello world的なサンプルを作ることにしました。

目標

PrismのRegionで、「Window(枠)は1つで内側の表示を切り替える」だけのサンプルアプリを作る。

ところが、Regionの使い方がわからない、ビルドが通らない、Viewを表示してもRegionに何も表示されない・・・なかなかにハマってしまいました。さんざん調べまくり、ようやく、参考になる記事と公式サンプルに・・・たどり着いた、というよりは、記事の使い方が分かった、という感じです。結果としてどうすればよかったのか、ほかの初心者の方の参考になれば幸いです。

  • MainWindowに、Buttonを2つ、Region(ContentControl)を1つ配置。
  • Buttonによって、Regionに表示されるView(UserControl)を切り替える。
  • Regionに初期表示されるView(UserControl)を指定する。

Prismの公式サンプル
「17-BasicRegionNavigation」とやっていることは同じですが、Moduleを使わずに簡易化したものです。

Module化しないように作り変えるなんてMVVMの方向性に逆行しているのでしょうが、RegionManagerでNavigateすることだけを切り取るためにあえてそうしました。

環境

VisualStudio 2019
Prism Template Packインストール
Prism.Unity 7.2

プロジェクト作成

Prism Template Pack

Prismはライブラリでもあり、プロジェクト構築サポート機能??でもあるようです。
WPF(Prism)アプリ作成に先立ち、Prism Template Packをインストールしておきます。VisualStudio自体をグレードアップするようなイメージです。これにより、VisualStudioから Prismのプロジェクトテンプレートが使えるようになります。
VisualStudio MarketPlace

VS2017だとPrism公式サンプルが開けないようです。VS2019にしておきましょう。 (Prismのインストールがうまくいってなかっただけかも)

プロジェクト作成

VisualStudioのプロジェクト新規作成で、Prism Blank App (WPF)を選択。
DIコンテナにUnityを選択します。
一度ビルドすると、プロジェクトにPrism.Unityが組み込まれます(Prism Template Packのおかげらしい)。

公式サンプル「17-BasicRegionNavigation」では別プロジェクトにRegion埋め込み用のViewを作っていますが、ここでは簡略して同プロジェクトにします。

Regionに埋め込むView(UserControl)

プロジェクト内のフォルダ Views に対し、[追加]>[新しい項目の追加]で、Prism UserControlを選択します。クラス名(コントロール名)をViewAとします。同様にViewBを作成します。
image.png
image.png

Prismが、Viewに対応するViewModelのクラスの自動生成と、Viewのxamlにprism:ViewModelLocator.AutoWireViewModel="True"の付け足しをしてくれます。

Viewの作成

ViewA,ViewBの内容

公式サンプル「17-BasicRegionNavigation」と同様、それぞれ単にViewA, ViewB と表示するTextBlockを一つあるだけとします。下記はViewAのxamlのサンプル。

ViewA.xaml
<UserControl x:Class="BlankApp3.Views.ViewA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:prism="http://prismlibrary.com/"             
             prism:ViewModelLocator.AutoWireViewModel="True">
    <Grid>
        <TextBlock Text="ViewA" FontSize="48" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</UserControl>

このサンプルではViewModelでは何もしません。コードも自動生成のままでよいです。INavigationAwareを実装しなくてもとりあえずNavigationされるようです。

MainWindowのxaml

公式サンプル「17-BasicRegionNavigation」と同様、MainWindowに、Buttonを2つ、Regionを1つ配置します。ButtonによってRegionの内容をViewA⇔ViewB切り替えできるようにします。

自動生成されたMainWindowView.xamlに対し、Buttonを2つ追加します。ContentControlは大きさだけ調整します。
ButtonのCommandには、ViewModelに定義するNavigateCommandというプロパティをバインドします。さらにCommandParameterには、切り替えるViewのクラス名を指定します。ViewA.xamlならViewAです。

MainWindow.xaml
<Window x:Class="BlankApp3.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://prismlibrary.com/"
        prism:ViewModelLocator.AutoWireViewModel="True"
        Title="{Binding Title}" Height="200.784" Width="334.091">
    <DockPanel LastChildFill="True">
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="5" >
            <Button Command="{Binding NavigateCommand}" CommandParameter="ViewA" Margin="5">Navigate to View A</Button>
            <Button Command="{Binding NavigateCommand}" CommandParameter="ViewB" Margin="5">Navigate to View B</Button>
        </StackPanel>
        <ContentControl prism:RegionManager.RegionName="ContentRegion" Margin="5"  />
    </DockPanel>
</Window>

image.png
ViewModelのコード変更は後述します。

RegionManagerとDI

【備考】Prism7.1 以降でアプリ起動時のコードが変更されています。過去の人気記事とは少し違うことに注意してください。

Region上のViewを切り替えることとDI(Dependency injection)とは関係ありませんが、Prismを使うにあたってDIコンテナの使用が前提となります。RegionManagerのクラス登録はPrismが作ってくれるらしく、自分のコード上では何も指定しません。一方で、自分で作るViewはクラス登録が必要です。Viewの切り替え(Navigation)に使えるように、DIコンテナに、ViewA, ViewBのクラスを登録します。※ViewA,ViewBのインスタンスの生成・破棄のタイミングは別途指定が必要になるはずです。未調査&割愛。

App.xaml.cs
using BlankApp3.Views;
using Prism.Ioc;
using Prism.Modularity;
using System.Windows;

namespace BlankApp3
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App
    {
        protected override Window CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<Views.ViewA>();
            containerRegistry.RegisterForNavigation<Views.ViewB>();
        }
    }
}

ViewA, ViewBを切り替える仕組みとして、MainWindowViewModel内でRegionManagerを使います。そのRegionManagerはDIでもらいます。

ところで、PrismでViewModelがRegionManagerをDIコンテナからもらう(サンプルの)コードには2種類あります。プロパティでもらう場合と、コンストラクタでもらう場合です。

プロパティでもらうには、Prism.Unityとは別に、NuGetでUnity(作成者: Unity Container Project)をインストールします。[Dependency]を指定するにはusing Unityが必要です。ちなみに過去の人気記事ではusing Microsoft.Practices.Unityとなっていますが、2016年頃にdllが変わったようです。
image.png

using Unity;

namespace BlankApp3.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        [Dependency]
        Prism.Regions.IRegionManager RegionManager { get; }

のようにします。ただし、この場合はMainWindowViewModelのコンストラクタの時点では RegionManagerプロパティがnullです。従ってコンストラクタでRegionの初期表示Viewを指定することはできません。インスタンスが作成された後、何かのイベントで初めて使えるようになるようです。この記事には、MainWindowの(コードビハインドの)Activateイベントで行う例が紹介されています。

もう一つの方法、コンストラクタでもらう場合には、NuGetでUnityを取得する必要はありません。このサンプルはこちらを使います。

    public class MainWindowViewModel : BindableBase
    {
        private readonly IRegionManager _regionManager;

        public MainWindowViewModel(IRegionManager regionManager)
        {
            _regionManager = regionManager;

Regionに表示されるViewの切り替え(Navigation)と、初期表示指定

サンプルはMainWindow上のButtonでViewA, ViewBを切り替える、という仕様です。MainWindow.xamlの側ではButtonでCommand="{Binding NavigateCommand}"としています。そのNavigateCommandを、MainWindowViewModelに定義します。さらにCommandParameterを受け渡すためにstringを引数としたCommandとしています。public DelegateCommand<string> NavigateCommand { get; private set; }
このDelegateCommandでNavigate()メソッドを呼び出し、RegionManagerに対しViewを切り替える要求を出します。

初期表示の指定にはRegisterViewWithRegion()を使えるようです。切り替えの要求(RequestNavigateメソッド)では、初期表示を指定できませんでした。

まとめて、MainWindowViewModel.csはこのようになります。

MainWindowViewModel.cs
using Prism.Mvvm;
using Prism.Regions;
using Prism.Commands;

namespace BlankApp3.ViewModels
{
    public class MainWindowViewModel : BindableBase
    {
        private string _title = "Prism Application";
        public string Title
        {
            get { return _title; }
            set { SetProperty(ref _title, value); }
        }

        private readonly IRegionManager _regionManager;

        public DelegateCommand<string> NavigateCommand { get; private set; }

        public MainWindowViewModel(IRegionManager regionManager)
        {
            _regionManager = regionManager;
            _regionManager.RegisterViewWithRegion("ContentRegion", typeof(Views.ViewA));

            NavigateCommand = new DelegateCommand<string>(Navigate);
        }

        private void Navigate(string navigatePath)
        {
            if (navigatePath != null)
                _regionManager.RequestNavigate("ContentRegion", navigatePath);
        }
    }
}

参考

WPF PRISM 入門エントリまとめ
Prism公式サンプル
[WPF/C#]Prism(6.3.0)のRegionで画面遷移をする
Prism-Samples-Wpfの勉強メモ

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

【Unity(C#)】OculusQuestのハンドトラッキングでボタンを押す

Oculus Integration

この記事内ではOculus Integrationの使用を前提に話を進めていきます。

Oculus Integration内のコード自体は
私レベルでは理解不能なことをいっぱいやっていますが、
パーツを部分的に使用すれば本当に使いやすくなっているのが
今回使ってみて改めてよくわかりました。

ありがとうOculus。
(贅沢言えばハンドジェスチャーのステートとかほしい、、、)

デモ

作成中のお絵描きアプリでRedo,Undo機能の呼び出しにボタンを使いました。

HandPaint1.gif

準備

下記リンクを参考に、デモシーンからボタンをパクってきていろいろ設定します。

【参考リンク】:Oculus Quest ハンドトラッキングを試してみた

コード

一部を抜粋して貼ります。

        [SerializeField] private ButtonController _redoButtonObj, _undoButtonObj, _paintButtonObj;

        private HpPaintFunctionState _paintFunctionState;

        private void Start()
        {
            //Redoボタンが押されたらFunctionステートを変更
            _redoButtonObj.InteractableStateChanged.AddListener(modeChangeToRedo);

            //Undoボタンが押されたらFunctionステートを変更
            _undoButtonObj.InteractableStateChanged.AddListener(modeChangeToUndo);

            //Paintボタンが押されたらFunctionステートを変更
            _paintButtonObj.InteractableStateChanged.AddListener(modeChangeToPaint);
        }


        /// <summary>
        /// Redoモードに変更
        /// </summary>
        /// <param name="obj">リスナー登録時に必要な引数</param>
        private void modeChangeToRedo(InteractableStateArgs obj)
        {
            if (obj.NewInteractableState == InteractableState.ActionState)
            {
                _paintFunctionState = HpPaintFunctionState.Redo;
            }
        }

        /// <summary>
        /// Undoモードに変更
        /// </summary>
        /// <param name="obj">リスナー登録時に必要な引数</param>
        private void modeChangeToUndo(InteractableStateArgs obj)
        {
            if (obj.NewInteractableState == InteractableState.ActionState)
            {
                _paintFunctionState = HpPaintFunctionState.Undo;
            }
        }

        /// <summary>
        /// Paintモードに変更
        /// </summary>
        /// <param name="obj">リスナー登録時に必要な引数</param>
        private void modeChangeToPaint(InteractableStateArgs obj)
        {
            if (obj.NewInteractableState == InteractableState.ActionState)
            {
                _paintFunctionState = HpPaintFunctionState.Paint;
            }
        }

InteractableState.ActionStateという状態になっていれば
各機能(Redo,Undo,Paint)へ切り替わる
という関数を
それぞれのボタンのステートが切り替わった際に実行されるイベントとして登録しています。

まとめ

ButtonControllerInteractableStateChanged
 イベントを登録することでステート変化時に任意の処理が実行可能。

・登録するイベントにはInteractableStateArgsを引数で渡して
 どのステートからどのステートに移行したかに応じた処理が書ける。

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

ビルド時にgitのCommit Hashを定数として自動生成する

はじめに

バージョニングはアプリをデプロイする上で大事な要素です。
大抵の開発プラットフォームにはバージョン番号が設定できます。それを設定画面やトップ画面等で表示しているアプリもよく見かけます。
ですが「このアプリはいつ時点のコミットでビルドしたものなのか?」を確認したい場合、数字ではひと目でわかりません。
というわけで、ビルド前に自動で現在のコミットのハッシュ値を定数のクラスとして生成するEditor拡張を作成しました。

準備

Editorフォルダに格納する

CommitHashGenerator.cs

この.csEditorフォルダに格納します。
このとき、自動生成される.csのパスを変更したい場合は以下の箇所を書き換えます。

CommitHashGenerator_Line_28
// replace if necessary
private const string FilePath = "Assets/Scripts/Generated";

生成する

Commit.cs
public static class Commit
{
    public const string Hash = "390181b";
}

このような.csが自動生成されます。

手動生成

スクリーンショット 2020-03-20 1.40.16.png
Create -> CommitHashで手動で生成することができます。
importした直後はこれで.csを生成してください。

自動生成

IPreprocessBuildWithReport.OnPreprocessBuild
ビルドの開始前に呼ばれる上のコールバックのタイミングで.csを自動生成します。

.gitignore.gitkeep

Commit.csは別のコミットでビルドするたびに再生成されるため、.gitignoreに登録することをおすすめします。
しかしそのままだと空フォルダにUnity.metaを生成するので、.gitkeepを使ってフォルダの構成を維持しておくと便利です。

CI対応

JenkinsCircleCIなど、様々なCI/CDツールがあります。
やり方はプロジェクトによって異なると思うので、以下のpublic staticなAPIを公開してあります。

CommitHashGenerator_publicApi
/// <summary>
/// 引数のstringをHashに設定したCommit.csを生成します.
/// </summary>
public static void GenerateScript(string hash)

/// <summary>
/// 現在のcommitのhash値を取得します.
/// </summary>
public static Task<string> CheckCommitHashAsync()

BuildPipeline.BuildPlayerの実行前に上記のAPIを実行すれば生成されます。

使用例

スクリーンショット 2020-03-20 2.16.23.png
デフォルトだと上記のパスに生成されます。
画面に表示してみました。

SampleCommitHash
using UnityEngine;
using UnityEngine.UI;

public class SampleCommitHash : MonoBehaviour
{
    [SerializeField]
    private Text _text;

    private void Start()
    {
        _text.text = Commit.Hash;
    }
}

まとめ

これ作ってなかった自分、アホですわ!
って言いたくなるくらい便利です。
アプリのバイナリのバージョン確認やログ収集など、どの時点かのビルドかがわかるようになるとここまで便利になるとは……。

「リリースしたいんだけど、このバイナリいつビルドしたやつだろう……ヨシ!」1

みたいなことがなくせます。やったね!

前回の記事でMessagePack-CSharpのもろもろを解析した結果思いつきました。
OSSをちゃんと読むの大事ですね。肝心のSerialize/Deserializeしてるあたりはぜんぜんわかんなかったです。わからん。
手元に開発環境がmacしかないので、winとかlinuxとかの人は動かしてみて、だめだったらなんか適当に直しといてください。

おしまい。

CommitHashGenerator.cs

参考

【Unity】ビルド時にGitコミットハッシュをバージョン情報に含めた件
Git で現在のコミットのハッシュを取得する方法
(小ネタ)改行文字を削除する
スクリプトからテンプレを自動作成【Unity】【エディタ拡張】
Unityプロジェクトへファイルを追加する際に上書き更新する
【Unity】エディタ拡張で使用できるコールバックを40個まとめて紹介
Unityのエディタ拡張で設定を保存
メニューを追加するための属性「MenuItem」は意外と多機能【Unity】【エディタ拡張】【属性】


  1. もちろんしたことないですけどねそんなこと!!!?!?!!!?!!!?!?!?!?!?!???!!!! 

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

[Unity] UniversalRenderPipelineのRenderScaleをC#から変更する

はじめに

UniversalRenderPipelineのRenderScaleを実行時にC#から変更したいのでコードを書きました。
URPを使っている人向けの情報なので、URPのセットアップは説明は省略します。
ソースコードはここです。

↓結果画面
renderscale.gif

目的

実行時にC#からRenderScaleを変更して見え方を確認したできるようにします。

Editor操作で変更

インスペクタ上でRenderScaleを変更する場合、使っているUniversalRenderPipelineAssetのインスペクタを開きます。
Quality > RenderScaleのスライダーを動かすとRenderScaleが変更されます。

スクリーンショット 2020-03-19 23.31.04.png

C#から変更

GraphicsSettings.currentRenderPipelineでUniversalRenderPipelineAssetを取得すれば、あとはrenderScaleにアクセスできます。
0に近づけると解像度が低くなります。Default値は1です。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.UI;

public class RenderScaleWidget : MonoBehaviour
{
    [SerializeField]
    private Slider renderScaleSlider;

    private UniversalRenderPipelineAsset pipelineAsset;

    private void Start()
    {
        pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
        if (pipelineAsset != null)
        {
            renderScaleSlider.onValueChanged.AddListener(OnValueChange);
            OnValueChange(pipelineAsset.renderScale);
        }
    }

    private void OnValueChange(float value)
    {
        if (pipelineAsset != null)
        {
            pipelineAsset.renderScale = value;
        }
    }
}

さいごに

URPのRenderScaleを変更できるようになりました。
UniversalRenderPipelineAssetの他のパラメーターも変更できます。

URPの使い方をもっと知りたい方はこちらをどうぞ。
技術書典8 新刊
Unity実践UniversalRenderPipeline
技術書典応援祭
BOOTH

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