Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

Visual Studio Online Public preview 試してみた!

$
0
0

変更履歴

サービス名を Visual Studio Code Online と間違えて書いていたので Visual Studio Online に修正しました。

本文

Microsoft Ignite 2019 のキーノート見てたらまさかの!!ということで以下のページから試してみました。

online.visualstudio.com

f:id:okazuki:20191105004131p:plain

サインインして環境を作る

開始するを選ぶとサインインする画面になります。

f:id:okazuki:20191105004211p:plain

以下のように Azure サブスクリプションとかを選ぶようになったのですが課金は Azure いるのかなぁ???Azure サブスクリプション持ってない人がどういうフローになるのか気になる…。

f:id:okazuki:20191105005304p:plain

環境のカスタマイズとかは VS Code のリモート開発の devcontainer.json とかで出来るみたい!

docs.microsoft.com

とりあえず今回はそのまま作ってみた。そして、環境を作ります。Create environment ボタンから。ここで強いマシンか弱いマシンが選べます。弱いマシンでも Linux 4 cores, 8 GB RAM なのでとりあえず困らなさそう。

f:id:okazuki:20191105010152p:plain

ちなみに Azure ポータルを覗いてみたら VS Online のリソースが作られてました。

f:id:okazuki:20191105010325p:plain

料金

フルマネージドの環境を使う場合は課金されるという感じなので、無料で使える感じではないですね。

azure.microsoft.com

100 時間アクティブに使って月額 6,000 円いかないくらいが想定されてるみたいです。20 日稼働で毎日 6 時間開いたとしたら 120 時間なので、まぁもうちょっと行きそうかな?

開いてみよう

プランを作ると、こんな感じになるのでクリックすると…

f:id:okazuki:20191105010517p:plain

そのまんま Visual Studio Code の画面!ターミナルを開くショートカットやコマンドパレットのショートカットも効きます。

f:id:okazuki:20191105010613p:plain

なんとなく dotnetコマンド叩いてみると .NET Core 2.1 が入ってた。

f:id:okazuki:20191105010722p:plain

OS が気になったので確認してみたら Debian GNU.Linux 9 らしいです。

vsonline:~$ cat /usr/lib/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
VERSION_CODENAME=stretch
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

ローカルの VS Code の設定を同期

Settings Sync拡張機能で設定を同期してるのですが、VS Online で Settings Syncを入れて LOGIN WITH GITHUB するとうまくできませんでした。なので Download Public Gistから Gist ID を指定して入れました。Gist ID は自分の Settings Sync で同期に使ってる Gist の URL の最後のランダムな文字列部分です。 (最初 URL をそのまま貼ってしまい失敗しました。)

こんな感じで色々勝手にインストールしてローカル環境と揃えてくれるのでありがたいです。

f:id:okazuki:20191105012318p:plain

.NET Core 3.0 を入れてみよう

なんとなく入れてみました。

dotnet.microsoft.com

ここらへんを手動でやったけど、devcontainer.jsonとかをちゃんと準備してるとやらなくていい感じなんだろうなぁきっと。 ということで入りました。

f:id:okazuki:20191105011549p:plain

せっかくなので Blazor のプロジェクトを作って実行してみたいと思います。

vsonline:~/workspace$ dotnet new blazor -o HelloBlazor

コマンドパレットから Open Folderを実行して開きます。

f:id:okazuki:20191105013010p:plain

ちゃんと C# 拡張機能が仕事してます。

f:id:okazuki:20191105013127p:plain

念のため Reload Windowして、適当なファイルを開いてインテリセンスを試してみるとちゃんと動いてる。

f:id:okazuki:20191105013245p:plain

おもむろに F5 キーを押してデバッグしてみます。 自動的にポートフォワーディングしてくれそうな雰囲気を見せつつ、なんかダメでした。

f:id:okazuki:20191105013401p:plain

ちゃんと設定は出来てるっぽいのになぁ。後でドキュメント見てみよう。

f:id:okazuki:20191105013601p:plain

ということで、ローカルの Visual Studio Code に Visual Studio Online 拡張機能を入れて VS ONLINE に繋いてみました。VS Code Online の先にリモート開発機能でつないでる感じですね。

f:id:okazuki:20191105013700p:plain

こっちは普通のリモート開発と同じ要領で実行とデバッグ出来ました。

f:id:okazuki:20191105014026p:plain

感想

ブラウザーで VS Code が動くのはまじで便利ですね!iPad とかでも開発がはかどりそう(iPad Pro 手放しちゃった!!無念!!) まぁでも、普通はローカルの VS Code からリモート開発で使うかな。ブラウザーは補助的な立ち位置。何故なら当然の動作なのですがフォーカスの位置がブラウザーのウィンドウの方に行ってる状態で VS Code のショートカットを気持ちよくたたくとブラウザーがリフレッシュしたりプリントダイアログが出てきたりして、ちょっと悲しい気持ちになったりしたので、可能であるならローカルの VS Code を使った方が快適なのは間違いないです。

でも VS Code が入らない端末や、ほかの人のマシンのプライベートブラウズからでも自分の環境を触ろうと思えば触れるのは素敵。

ということでやってみたブログでした!


Livet v3.2.1 をリリースしました

$
0
0

しばらく触ってませんでしたが、リリースしました。

Microsoft.Xaml.Behaviors.Wpf が .NET Core 3.0 対応したので、Livet も警告が出ない状態で .NET Core で使えます。

変更点

v3 系になってから各種機能を個別の NuGet パッケージに分離してきましたが以下のような形で落ち着きました。

LivetCask パッケージ

NuGet Gallery | LivetCask 3.2.1

以下のパッケージの詰め合わせです。

  • LivetCask.Behaviors
  • LivetCask.Collections
  • LivetCask.Converters
  • LivetCask.EventListeners
  • LivetCask.Messaging
  • LivetCask.Mvvm

従来通りの機能セットが含まれるように各種パッケージをまとめてます。

LivetCask2 パッケージ

NuGet Gallery | LivetCask2 3.2.1

以下のパッケージの詰め合わせです。

  • LivetCask.Behaviors
  • LivetCask.StatefulModel
  • LivetCask.Converters
  • LivetCask.EventListeners
  • LivetCask.Messaging
  • LivetCask.Mvvm

従来のコレクションを StatefulModel から取り込んだコレクションに置き換えています。

個別利用

各種パッケージは個別に入れて利用できます。Prism や MVVMLight などを使いつつ LivetCask.EventListeners, LivetCask.Converters, LivetCask.Behaviors, LivetCask.StatefulModel などを追加し利用してください。

これらのパッケージはほかの MVVM 系のライブラリーとは競合しないようなパッケージになってると思います。

ReactiveProperty v6.1.4 をリリースしました

$
0
0

各種ライブラリの更新と Pull Request の取り込みになります。 大きな変更はありません。

導入は以下の NuGet から

www.nuget.org

余談

余談ですが、最近個人的に気になってる Uno Platform でも試してみましたが動かすのに成功しませんでした…。 う~ん。WebAssembly 上なのでスレッドの制約やらなんやらかなぁ。

SDK StyleのプロジェクトファイルをUWP, Xamarinで使いたい

$
0
0

ReactiveProperty のパッケージ分割するにあたってやろうとしてるんだけど標準だときびしい…?ということで MSBuild.Sdk.Extrasというパッケージを使いました。

空のクラスライブラリを作って Sdk属性に MSBuild.Sdk.Extras/2.0.54を設定して TargetFrameworksを以下のように設定。

<Project Sdk="MSBuild.Sdk.Extras/2.0.54"><PropertyGroup><TargetFrameworks>uap10.0.18362;MonoAndroid90;Xamarin.iOS10</TargetFrameworks><GeneratePackageOnBuild>true</GeneratePackageOnBuild></PropertyGroup></Project>

するととりあえずビルドして、今回はクラスライブラリで GeneratePackageOnBuildも設定してるので NuGet パッケージもできました。

f:id:okazuki:20191202090636p:plain

一応それぞれにむけてのフォルダーがあって、その中に dll が出来てました。

f:id:okazuki:20191202090727p:plain

ところで、この場合ミニマムなサポートバージョンは何処で指定するのだろう??

Visual Studio の region で囲んで整理されたソースコードを見るのが辛いときの操作

$
0
0

整理整頓されてるのはいいのですが全体見たいときにいちいち展開していくのがだるいときないですか?

例えばこんなコードで…

f:id:okazuki:20191204105833p:plain

一段展開するとこうなって…

f:id:okazuki:20191204105857p:plain

辛い…。

緩和方法

region で囲まれて折りたたまれてるファイルが表示されたら Ctrl + M, P (右クリックメニューのアウトラインの中にあるアウトラインを止める機能のショートカット)を押してアウトラインを止めると regionが一気に展開されます。

f:id:okazuki:20191204110134g:plain

アウトラインが死んでしまいますが、再度ファイルを開きなおすとちゃんとアウトライン機能は復活して region は展開されたままになります。

Hello ML.NET world

$
0
0

.NET Core 3.1 もリリースされたし ML.NET でも少し触ってみようと思います。今はカフェで Surface Go しか持ってないので先日作って寝かせておいた VIsual Studio Online につないでやってみようと思います。

.NET Core 3.1

このページに従ってコマンドをうってたらインストールできます。

docs.microsoft.com

dotnet --list-sdksして入ってることを確認しましょう。

vsonline:~/workspace/HelloMLNET$ dotnet --list-sdks
2.1.802 [/usr/share/dotnet/sdk]
3.0.100 [/usr/share/dotnet/sdk]
3.1.100 [/usr/share/dotnet/sdk]

プロジェクトを作ってためしてみる

とりあえず以下のページの通りにやっていきます。まず写経。

docs.microsoft.com

dotnet new console -o HelloMLNETとうってコンソールアプリのプロジェクトを作って、そのフォルダーを VS Code で開きます。

dotnet add package Microsoft.MLで ML.NET のパッケージを追加して写経モードに入ります。

using System;
using Microsoft.ML;
using Microsoft.ML.Data;

namespace HelloMLNET
{
    class HouseData
    {
        publicfloat Size { get; set; }
        publicfloat Price { get; set; }
    }

    class Prediction
    {
        [ColumnName("Score")]
        publicfloat Price { get; set; }
    }

    class Program
    {
        staticvoid Main(string[] args)
        {
            var mlContext = new MLContext();

            var houseData = new[]
            {
                new HouseData { Size = 1.1F, Price = 1.2F },
                new HouseData { Size = 1.9F, Price = 2.3F },
                new HouseData { Size = 2.8F, Price = 3.0F },
                new HouseData { Size = 3.4F, Price = 3.7F },
            };

            var trainingData = mlContext.Data.LoadFromEnumerable(houseData);

            var pipeline = mlContext.Transforms.Concatenate("Features", new[] { nameof(HouseData.Size) })
                .Append(mlContext.Regression.Trainers.Sdca(
                    labelColumnName: nameof(HouseData.Price), 
                    maximumNumberOfIterations:100));

            var model = pipeline.Fit(trainingData);

            var size= new HouseData { Size = 2.5f };
            var price = mlContext.Model.CreatePredictionEngine<HouseData, Prediction>(model).Predict(size);

            Console.WriteLine(price.Price);

        }
    }
}

これを実行すると

2.7478235

と表示されました。参考にしたサイトの結果と数字が違うのは何か間違えたのか、それとも ML.NET のバージョンが変わって何か微妙に変わったのかよくわからないけど、元ネタのデータ(今回の場合 houseData)を作って、トレーニングして実際に Predict メソッドで結果出してる感じですね。

モデルのテストとかはしてないので、本番はもうちょいコードは長くなると思いますが、何となくこんな感じでいけるんだなぁという感じ。

ML.NET のルール

ルールというか使い方なのですが、ML.NET は MLContext クラスにすべてが詰まってる感じです。

例えば

var mlContext = new MLContext();


mlContext.Data // データの読み書き関係の処理が詰まってる
mlContext.Transforms // データの変換系が詰まってる
mlContext.Regression // 回帰分析系かな?
mlContext.BinaryClassification // 2項文累計かな?

みたいな感じです。

とりあえず動いたので引き続き触っていってみよう。

Azure Functions v3 試せるようになってる

$
0
0

試せますね!!ということで早速。VS 2019で試すにはテンプレートとかを出すために環境変数設定して…とかがいりそうなので CLI でさくっとやってみました。

Core tool の更新

特別なことは何もなし。コマンドでさくっと。

npm install -g azure-functions-core-tools@3

プロジェクト作成

func initして dotnetを選択した後にプロジェクトファイルを書き換えます。

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netcoreapp3.1</TargetFramework><AzureFunctionsVersion>v3</AzureFunctionsVersion></PropertyGroup><ItemGroup><PackageReference Include="Microsoft.NET.Sdk.Functions"Version="3.0.1" /></ItemGroup><ItemGroup><None Update="host.json"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None><None Update="local.settings.json"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory><CopyToPublishDirectory>Never</CopyToPublishDirectory></None></ItemGroup></Project>

TargetFramework を .NET Core 3.1 向けにして、AzureFunctionVersion を v3 にします。PackageReference の Microsoft.NET.Sdk.Functions を 3.0.1 にします(ブログ見たら 1.0.30 のプレビュー使うように書いてあったけど、3.0.1 の方が新しそう)

func newで Hello という名前の HttpTrigger の関数を作ってそのまま func startしてみましょう。作った関数を叩いてみると面白味も何もなく普通にすかっと動きました。

f:id:okazuki:20191209185057p:plain

これだと本当に .NET Core 3.x 上で動いてるかすらわからないので、Environment.Version でもレスポンスに含めてみましょう。

new OkObjectResult($"Hello, {name}. This function run on {Environment.Version}")

そして実行すると…

f:id:okazuki:20191209185333p:plain

うん。

Azure で動かす場合

Azure で適当にコンサンプションプランの Function App を作成します。作成したら関数アプリの設定を見ます。v3 があるので選択しましょう。

f:id:okazuki:20191209185813p:plain

func azure functionapp publish <デプロイ先の関数アプリ名>でさくっとデプロイ。

今度は POST で試してみましょう。

f:id:okazuki:20191209190532p:plain

ばっちり動きましたね!!!

まとめ

まだ、本番運用向けではありませんが .NET Core 3.x 向けの Azure Functions も着々と開発が進んでるので GA が楽しみです!

Azure Functions v3 リリースしてた

$
0
0

先日… 12 月 9 日に v3 のプレビュー試せるやったね~ってやりました

blog.okazuki.jp

試せるようになってるのに気づいたのが結構出来るようになってから立った後だったのですが、ふと見てみたら 12 月 9 日くらいに GA してました。

azure.microsoft.com

現時点で v1 と v2 がいつサポートが終わるというのはアナウンスされてないですし、アナウンスも 1 年前にはされるらしいので、今すぐ移行!というわけではないですが、.NET Framework を使用しないといけないという縛りが無い限りは v1 も v3 へ、v2 も v1 へ移行しておいたほうが将来的には安泰な気がします。

GA したので Visual Studio の Azure Functions の新規作成にも Azure Functions v3 (.NET Core) が追加されています。

f:id:okazuki:20191212140620p:plain

まだ、ポータルの UI 上はプレビューになってますが、そのうち取れるのだと思います。

f:id:okazuki:20191212141347p:plain

ということで、LTS 版の .NET Core 3.1 に移行できるやつは移行してしまったほうがよさそうですね。


WPF on .NET Core 3.1 で Windows 10 アプリ開発というタイトルで牛タン会議 2019 で登壇してきました

問題調査でドツボにはまった状態から抜け出す技術?というか気にしてるところ

$
0
0

なんとなく、牛尾さんの以下の記事を見て自分が気を付けてることをメモしておこうかなと思ったのでメモ。

qiita.com

まずハマらないために

何か新しいことをやるときは、それだけにフォーカス出来る状態で練習を一度する

既存のプログラムとかに〇〇を組み込もう!!というときは〇〇の中で使いそうなものを当たり前ですが事前にきちんと勉強する時間をとるとトータルで早く終わることが多いです。 きちんと勉強しなくても、クイックスタートを見ながら自分のプロジェクトに追加とかもできますが、これはうまくいくと一番最速で取り込めるけど、うまくいかなかったときに正しい最短手順を知らない状態なので、あれが足りないのかなぁ?これが足りないのかなぁ?あれ?さっき足したあれって実はいらないのかなぁ?とか試行錯誤が入ると、経験的にちゃんと学習して取り組んだほうが最終的には早くなると思ってます。

背伸びはなるべくしない

何かやろうとしてわからないときは、焦らずわからないものに関して上でかいた、それだけにフォーカス出来るようなかき捨てプログラムを書いて足場を固めてから、その上で本当にやりたいことに取り組む。

これは、やってるときりがないというのはあるけど、自分が使いたいものに直接関連してるものへの理解度は、ハマらない確率を上げる重要な要素だと思ってます。 なので、背伸びしなくても出来るようにまわりの足場はきっちり固めて土台を積み上げて取り組もう。

ブログを書く

「〇〇について完全に理解した」と思ったり、そこに少しでも近いづいたらブログに書く。 数日~数年たって「〇〇について完全に忘れた」状態になっている将来の自分が読んで喜ぶようなものを書いておくとはかどる。

ついでに、ほかの人にも喜ばれる。最高。

ハマったときは

ここは大丈夫だろうという思い込みを無くす

ハマってるときは何か間違えてるのだけど、無意識に「〇〇は大丈夫なはず」だという思い込みで本当に基本的な部分をすっ飛ばして応用的な部分にフォーカスしていることが多いなぁと自分では思ってます。

なので、ハマってる人に相談されたときはハマってる内容を聞いたら一つずつ、そこに至るまでの手順を遡って確認するようにしています。 そうすると大体、このライブラリ使ってエラーになってるからコードが何処か間違えてるという状態から、実はライブラリをつかうための依存関係がきちんと定義されていないとか、そういう所で問題が見つかったりします。

ハマってる時は、大体思い込みや無意識で確認を飛ばしてる場所に間違いがあることがあるので、人に相談されたらその人がフォーカスしてない部分について確認するようにしようと思ってます。

ハマらないために書いたことをやる

「ハマらないために」の部分で書いた内容をやってない場合はやります。 結果として、ハマらず出来るようになることもある。

相談する

自分でどうしても解決できないことは、自分の頭の中で気づけない部分でハマってるので別の視点で見てくれる人に相談しよう。 そうすると、きっと自分がやってるちょっとしたミスに気付いてくれると思う。

まとめ

思ったことをつらつらと書いただけですが、なんとなく自分はこうしてるかなぁということを過去に書いたことが無かった気がしたのでメモがてら。

基本大事ですよね。

Unity でも DI 使ったりしたいし画面もいい感じに作りたい「Zenject & UIWidgets」 その 1

$
0
0

表題の通りです。Zenject を使ったり UIWidgets を使ってみたいと思います。

ここで使うライブラリと環境

環境

  • Windows 10 1909
  • Unity 2019.2.12f1

Zenject

Unity 向けに特化された DI コンテナで、Unity 向けの各種便利機能が追加されています。他の .NET Standard 対応の DI コンテナを Unity で使うことも出来ると思いますが、ビヘイビアー系を DI コンテナに追加したり、シーンを跨いでシングルトンで管理したいものとかまで考えると大変なので、おとなしく Zenject を使うのがいいと思いました。

github.com

UIWidgets

いい UI コンポーネントのアセット無いかなぁと探していたら見つけました。Flutter と同じノリで Unity でコード書けるらしいです。いいね。

github.com

Zenject の導入

Zenject の GitHub のリリースページから最新のバージョンの Zenject のサンプルの入っていないパッケージをダウンロードしてインポートします。

github.com

基本となるシーンの作成

インポートしたら Scripts フォルダーを作って、そこに Assembly Definition を作成して選択します。Assembly Definition References に項目を一つ追加して Zenject を追加しましょう。 シングルトンで管理したいオブジェクトを管理するためのシーンを作ります。base という名前で作って Hierarchy で右クリックして Zenject の SceneContext を作成します。

Scripts の下に BaseInstaller.csを作って以下のように適当にシングルトンとして登録します。今回はカウンタークラスを作っておきました。

using Zenject;

namespace UnityAppTest
{
    publicclass BaseInstaller : MonoInstaller
    {
        publicoverridevoid InstallBindings()
        {
            Container.Bind<Counter>().AsSingle();
        }
    }

    publicclass Counter
    {
        publicint Value { get; private set; }

        publicvoid Increment() => Value++;
    }
}

SceneContext に BaseInstaller.cs をアタッチして、SceneContext の MonoInstallers に対して BaseInstaller を設定します。最後に他のシーンから参照するために Contaract Names の Size を 1 にして base と入れましょう。

それぞれのシーンの作成

では、main という名前のシーンをさk末井して Hierarchy に追加しましょう。これで base と main が Hierarchy に表示されます。main にも SceneContext を作って Parent Contract Names の Size を 1 にして base と入れます。

そして、適当に Text を置いて画面の中央に表示されるようにします。

f:id:okazuki:20191218165727p:plain

MonoBehaviour を一つ作ります。TextUpdater という名前にしました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Zenject;

namespace UnityAppTest.Main
{
    publicclass TextUpdater : MonoBehaviour
    {
        [Inject]
        public Counter Counter { get; set; }

        private Text _text;

        privatevoid Start()
        {
            _text = GetComponent<Text>();
        }

        privatevoid Update()
        {
            _text.text = $"The counter value is {Counter.Value}";
        }
    }
}

そして、Text に TextUpdater を割り当てます。この時点で実行するとカウンターは 0 のままです。適当に Button を画面において以下のようなスクリプトを書いて

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace UnityAppTest
{
    publicclass SceneLoader : MonoBehaviour
    {
        publicstring UnloadSceneName;
        publicstring LoadSceneName;

        publicvoid Execute() => StartCoroutine(ExecuteImpl());

        private IEnumerator ExecuteImpl()
        {
            yieldreturn SceneManager.LoadSceneAsync(LoadSceneName, LoadSceneMode.Additive);
            yieldreturn SceneManager.UnloadSceneAsync(UnloadSceneName);
        }
    }
}

ボタンに張り付けて UnloadSceneName に main、LoadSceneName に next を設定します。そして、onClick に Execute メソッドを割り当てておきます。

next という名前のシーンを作って、SceneContext を追加します。SceneContext の Parent Contract Names の Size を 1 にして値に base を設定します。 そしてボタンを適当に 2 つ配置します。位置とテキストを調整します。

f:id:okazuki:20191218171950p:plain

Incrementer という MonoBehaviour を作って以下のような感じでカウンターをインクリメントします。

using UnityEngine;
using Zenject;

namespace UnityAppTest.Next
{
    publicclass Incrementer : MonoBehaviour
    {
        [Inject]
        public Counter Counter { get; set; }

        publicvoid Execute() => Counter.Increment();
    }
}

Increment と書かれたボタンにアタッチして Execute を onClick に割り当てます。Back というボタンには前に作成した SceneLoader を追加して UnloadSceneName に next を、LoadSceneName に main を指定します。

動かしてみよう

base と main が読み込まれた状態で next は unload して実行してみましょう。

f:id:okazuki:20191218173143g:plain

いい感じですね!!ということで次は UIWidgets を導入して画面をそれっぽくしてみます。次の記事でね!

Unity でも DI 使ったりしたいし画面もいい感じに作りたい「Zenject & UIWidgets」 その 2

$
0
0

前回は Zenject 使って複数シーンを跨いで有効なシングルトンなオブジェクトを作ってみました。 アプリ全体で管理したい情報などは、こういうのを使って管理したりすると捗りそうです。

blog.okazuki.jp

UIWidgets を入れてみよう

ということで今は The Unity って感じの見た目をしているのでカウンターの値を表示する部分を UIWidgets でそれっぽくしてみたいと思います。UIWidgets を使うと画面遷移とかもその場でサクッと出来そうなので、今回のようなものではシーンをわける必要すらないのですが、3D 世界と 2D 世界を行き来するときはシーンでわけたほうがやりやすいケースとかもあるかもしれません。

導入

UIWidgets の GitHub のリポジトリーのリリースページからソースコードを zip で落とします。zip を解凍したフォルダーを Unity のプロジェクトフォルダーの Packages フォルダーにコピーします。 Material Icons を使うためにフォントも入れておきましょう。以下のサイトから ttf 形式をダウンロードします。

github.com

そして、コルーチンを await したかったので UniRx.Async も以下のページからダウンロードして入れました。

github.com

追加したら、Scripts フォルダーの下に作った Assembly Definition に Unity.UIWidgets と UniRx.Async を Assembly Definition References に追加します。

Zenject と UIWidgets を一緒に使うためのちょっとした下準備をしておきます。ZenjectStatefulWidget クラスを定義して以下のように変更します。

using Unity.UIWidgets.widgets;
using Zenject;

namespace UnityAppTest
{
    publicclass ZenjectStatefulWidget<TSelf, TState> : StatefulWidget
        where TSelf : StatefulWidget
        where TState : State
    {
        [Inject]
        public DiContainer Container { get; set; }
        publicoverride State createState() => Container.Resolve<TState>();

        publicstaticvoid InstallToContainer(DiContainer container)
        {
            container.Bind<TSelf>().AsTransient();
            container.Bind<TState>().AsTransient();
        }
    }
}

DI コンテナにステートとウィジェットの両方を追加したいので、登録漏れが少なくなるように登録用のメソッドも用意しておきました。

まず、最初の画面を作りましょう。先ほど作った ZenjectStatefulWidget を継承する形の Widget と State を定義します。

class HomeWidget : ZenjectStatefulWidget<HomeWidget, HomeState>
{
}

class HomeState : State<HomeWidget>
{
    [Inject]
    public Counter Counter { get; set; }
    publicoverride Widget build(BuildContext context)
    {
        returnnew Theme(
            data:new ThemeData(),
            child:new Scaffold(
                appBar:new AppBar(
                    title:new Text(data: "Counter app"),
                    actions:new List<Widget>
                    {
                        new IconButton(
                            icon:new Icon(Icons.navigate_next),
                            onPressed: () => Navigator.of(context).pushNamed("/increment")
                        ),
                        new IconButton(
                            icon:new Icon(Icons.web),
                            onPressed: async () =>
                            {
                                await SceneManager.LoadSceneAsync("next", LoadSceneMode.Additive);
                                await SceneManager.UnloadSceneAsync("main");
                            }
                        )
                    }
                ),
                body:new Container(
                    padding: EdgeInsets.all(20),
                    child:new Text(data: $"This is a sample: {Counter.Value}")
                ),
                floatingActionButton:new FloatingActionButton(
                    tooltip:"Increment",
                    child:new Icon(Icons.add),
                    onPressed: () => {
                        setState(() => Counter.Increment());
                    }
                )
            )
        );
    }
}

State も Zenject の DiContainer からインスタンスを作る感じでやるので、Inject 出来ます。あとは適当に画面を作ってインクリメント用のボタンなんかも用意してます。 画面上部に /incrementと別のシーンへ行くためのボタンも用意しました。

次は /incrementで遷移する先の画面も作ります。

class IncrementWidget : ZenjectStatefulWidget<IncrementWidget, IncrementState>
{
}

class IncrementState : State<IncrementWidget>
{
    [Inject]
    public Counter Counter { get; set; }

    publicoverride Widget build(BuildContext context)
    {
        returnnew Theme(
            data:new ThemeData(),
            child:new Scaffold(
                appBar:new AppBar(
                    title:new Text(data: "Increment"),
                    leading:new IconButton(icon: new Icon(Icons.arrow_back), onPressed: () => Navigator.pop(context))
                ),
                body:new Container(
                    padding: EdgeInsets.all(20),
                    child:new Column(
                        children:new List<Widget>
                        {
                            new IconButton(icon: new Icon(Icons.add), tooltip: "Increment", onPressed: () => setState(() => Counter.Increment()))
                        }
                    )
                )
            )
        );
    }
}

画面上部のアプリバーに戻るボタンと、画面内にインクリメントするボタンがあるだけです。この画面からはカウンターの値が今いくつなのかは見ることは出来ません。まぁ見れてもいいんですが、前のページと同じになってしまうので、なんとなく出さないようにしました。本当に不便です。

ここまで出来たら UIWidgetsPanel を継承したクラスを作ります。このクラスを Panel のコンポーネントとして追加することで画面が描画されます。

publicclass CounterApp : UIWidgetsPanel
{
    [Inject]
    public DiContainer Container { get; set; }
    protectedoverridevoid OnEnable()
    {
        Screen.fullScreen = false;
        FontManager.instance.addFont(Resources.Load<Font>(path: "fonts/MaterialIcons-Regular"), "Material Icons");
        base.OnEnable();
    }

    protectedoverride Widget createWidget()
    {
        if (Container == null)
        {
            returnnew MaterialApp(home: new Scaffold());
        }

        returnnew MaterialApp(
            home: Container.Resolve<HomeWidget>(),
            routes:new Dictionary<string, WidgetBuilder>
            {
                ["/increment"] = ctx => Container.Resolve<IncrementWidget>(),
            }
        );
    }
}

フォントの読み込みと、/incrementという名前が来たときに IncrementWidget が生成されるような定義が入っています。また、何度やっても Container プロパティがインジェクトされる前に createWidgetが呼ばれて、Container がインジェクトされた後に、また createWidget が呼ばれるとか、あとは再生をしてないときは常時 Container が null みたいなので Container が null の時は真っ白な画面を出すだけにしました。

そして、main シーンにある Canvas を削除します。そして Panel を 1 つ作成して Image コンポーネントを削除します。Image コンポーネントを削除したら、先ほどの CounterApp を Panel のコンポーネントに追加します。この状態でエディター上で実行してみると以下のようになりました。ちゃんと動いてる。

f:id:okazuki:20191219161604g:plain

実機で動かそう

実機向けのビルドにあたって少しだけコードをいじって base シーンが読み込まれたときに main シーンも追加で読み込むスクリプトだけ足します。まぁ、これは難しくないのでよしなに。

ビルド時には base, main, next の 3 つのシーンが入っていることを確認します。特に base が初期シーンとして読み込まれるように一番上に持っていきます。そして、今回は Android 向けにビルドしました。実機で動かしてみると…

f:id:okazuki:20191219170432g:plain

ちゃんと動いてる。やったね!

ソースコードは以下の github のリポジトリに上げておきました。

github.com

Unity の UIWidgets で背景が透明なところは裏側の 3D の世界にイベントを飛ばしてほしい

$
0
0

デフォルトだと UIWidgets が全てかっさらってしまって、例えば Cube に EventTrigger をしかけて、UIWidgets に全面透明な画面とかを置いてもクリックが反応しないみたいです。 解決策として、以下の Issue にコードが載ってます。

github.com

一応念のためコードだけここにも複製。

using Unity.UIWidgets.engine;
using UnityEngine;
using UnityEngine.UI;

[DisallowMultipleComponent]
[RequireComponent(typeof(UIWidgetsPanel))]
publicclass UIWidgetsRaycastFilter : MonoBehaviour, ICanvasRaycastFilter
{
    publicbool reversed;

    private UIWidgetsPanel rawImage;

    void OnEnable()
    {
        rawImage = GetComponent<UIWidgetsPanel>();
    }


    publicstatic Texture2D toTexture2D(RenderTexture rTex)
    {
        Texture2D tex = new Texture2D(rTex.width, rTex.height, TextureFormat.RGBA32, false);
        RenderTexture.active = rTex;
        tex.ReadPixels(new Rect(0, 0, rTex.width, rTex.height), 0, 0);
        tex.Apply();
        return tex;
    }

    publicbool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        if (!enabled)
            returntrue;

        Texture2D texture = null;
        var w = rawImage.texture;
        texture = toTexture2D(w as RenderTexture);
        if (texture == null)
            returntrue;

        Vector2 local;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rawImage.rectTransform, screenPoint, eventCamera, out local);

        Rect rect = rawImage.rectTransform.rect;

        // Convert to have lower left corner as reference point.
        local.x += rawImage.rectTransform.pivot.x * rect.width;
        local.y += rawImage.rectTransform.pivot.y * rect.height;

        Rect uvRect = rawImage.uvRect;
        float u = local.x / rect.width * uvRect.width + uvRect.x;
        float v = local.y / rect.height * uvRect.height + uvRect.y;

        Debug.Log("alpha = " + texture.GetPixelBilinear(u, v).a);

        try
        {
            if (!reversed)
                return texture.GetPixelBilinear(u, v).a != 0;
            elsereturn texture.GetPixelBilinear(u, v).a == 0;
        }
        catch (UnityException e)
        {
            Debug.LogException(e);
        }

        returntrue;
    }
}

このコードを使うと透明な部分は Raycast が裏まで届くようになるらしい。原理は…知らない。 上記のスクリプトを Panel に追加して、以下のような画面を表示します。

class TransparentState : State<ThreeDementionInteractionWidget>
{
    publicoverride Widget build(BuildContext context)
    {
        returnnew Scaffold(
            backgroundColor: Colors.transparent,
            appBar:new AppBar(title: new Text("Transparent page"))
        );
    }
}

そうすると、こんな感じに動きます。

f:id:okazuki:20191219182512g:plain

一応、Android の実機にもデプロイして試してみたら同じように動きました。

VS Code Meetup #1 - 初回基礎編 で LT してきました

$
0
0

表題の通り、LT してきました。 デモがスムーズにいったので発表していて気持ちよかったです。

ほとんどがデモなので、あまり資料自体に中身はないですがお勧め拡張機能は書いてあるので資料はスライドシェアに上げました。

blog.okazuki.jp

Visual Studio Code の git の機能については過去に blog 記事を書いてますので、そちらを是非見てください。大体デモで紹介したことが書いてあります。

www.slideshare.net

イベントページ

vscode.connpass.com

C# で Discord のボットを書いて Azure にデプロイしてみよう

$
0
0

ゲーム好きの人たちにはおなじみの Slack みたいなチャットサービスのボットを C# でも作れます。Python が一応公式っぽい??

C# で Discord のボットを作る場合には Discord.NET というライブラリを使うのが一般的みたいです。

github.com

簡単に検索しただけでもチラホラ日本語記事がヒットします。

qiita.com

atriasoft.work

kagasu.hatenablog.com

Discord 側でのアプリの作成やボットのアカウントの作り方とか、特定のチャンネルにボットを追加する方法は上記のブログなどにしっかり書いてあるので、ここでは Azure にデプロイしてみたいと思います。

今回は .NET Core にある汎用ホストで作っていきます。

docs.microsoft.com

.NET Core のコンソールアプリを作成して、Microsoft.Extensions.HostingDiscord.NetMicrosoft.Extensions.Logging.Consoleという NuGet パッケージを追加します。

あとは淡々と書いていきます。

using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace DiscordBot.HostApp
{
    class Program
    {
        staticvoid Main(string[] args) => ConfigureHostBuider(args).Build().Run();

        publicstatic IHostBuilder ConfigureHostBuider(string[] args) => Host.CreateDefaultBuilder(args)
            .ConfigureLogging(b =>
            {
                b.AddConsole();
            })
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<DiscordBotService>();
            });
    }

    class DiscordBotService : BackgroundService
    {
        private DiscordSocketClient _client;
        privatereadonly IConfiguration _configuration;

        privatestring Token => _configuration.GetValue<string>("Token");
        public DiscordBotService(IConfiguration configuration)
        {
            _configuration = configuration;
        }

        protectedoverride async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _client = new DiscordSocketClient(new DiscordSocketConfig { LogLevel = Discord.LogSeverity.Info });
            _client.Log += x =>
            {
                Console.WriteLine($"{x.Message}, {x.Exception}");
                return Task.CompletedTask;
            };
            _client.MessageReceived += MessageReceived;
            await _client.LoginAsync(Discord.TokenType.Bot, Token);
            await _client.StartAsync();
        }

        publicoverride async Task StopAsync(CancellationToken cancellationToken)
        {
            await _client.StopAsync();
        }

        private async Task MessageReceived(SocketMessage arg)
        {
            if (!(arg is SocketUserMessage m) || m.Author.IsBot)
            {
                return;
            }

            await m.Channel.SendMessageAsync($"{m.Content} と言いましたね。");
        }
    };
}

プロジェクトには appsettings.json と appsettings.Development.json を追加します。

f:id:okazuki:20191219192150p:plain

appsettings.json と appsettings.Development.json は ASP.NET Core の Web アプリケーションプロジェクトから持ってきました。Development の方には以下のように Token を足しています。ここに Discord でとってきたトークンを入れます。

{"Logging": {"LogLevel": {"Default": "Debug",
      "System": "Information",
      "Grpc": "Information",
      "Microsoft": "Information"
    }},
  "Token": "<ここにトークンを入れる>"
}

これはプログラム中で以下のように Token という名前で設定を呼んでるから Token という名前で追加してるだけで、プログラムとあってれば任意の名前で大丈夫です。git を使う場合は、この appsettings.Development.json を .gitignoreに追加しておきましょう。

次にプロジェクトのプロパティで環境変数 DOTNET_ENVIRONMENTDevelopmentを追加します。これでローカルでのデバッグ実行時に appsettings.Development.json を見に行ってくれるようになります。

f:id:okazuki:20191219192513p:plain

実行するとなんか受付に対してレディになってますね。

f:id:okazuki:20191219192443p:plain

Discord を見てみるとボットがオンラインになってるので話しかけてみました。ちゃんと動いてます。

f:id:okazuki:20191219192716p:plain

Azure にデプロイ

では、今回はこの汎用ホストを WebJob にデプロイしてみようと思います。とりあえず Web App を作ります。Discord のボットは常駐してないと死んじゃうのでスタンダードプラン(S1)以上で作ります。

f:id:okazuki:20191219193139p:plain

そして、作成された App Service (Web App) の構成ページで新しいアプリケーション設定として Token を追加して Discord のボットのトークンを入れます。ローカルのデバッグ実行は appsettings.Development.json から読み込んで、本番はここから環境変数として読み込む感じです。ここら辺をいい感じに IConfiguration に隠してくれるの便利ですよね。

f:id:okazuki:20191219193830p:plain

本番で使うものは何かしらの CI/CD 系のサービスからデプロイするとして、今回のお試しはローカルの Visual Studio のプロジェクトの右クリックメニューから公開で WebJobs に公開していきます。

f:id:okazuki:20191219193352p:plain

先ほど作ったのが選択肢に出てくるので選びます。

f:id:okazuki:20191219193431p:plain

プロファイルが出来たら編集を押して Web ジョブの種類を Continuous にします。これで常時実行し続けます。

f:id:okazuki:20191219193539p:plain

では発行しましょう。発行が終わると、そのうち Discord のほうでボットがオンラインになります。

f:id:okazuki:20191219195308p:plain

ログぅ…

ダメ元で AddConsole メソッドを呼んでますが、WebJobs に置いただけでは Application Insights にはログはいてくれませんね。 なので Microsoft.Extensions.Logging.ApplicationInsightsを NuGet で追加して。ConfigureHostBuilder メソッドを以下のように書き換えます。

publicstatic IHostBuilder ConfigureHostBuider(string[] args) => Host.CreateDefaultBuilder(args)
    .ConfigureLogging((ctx, b) =>
    {
        b.ClearProviders();
        if (ctx.HostingEnvironment.IsProduction())
        {
            b.AddApplicationInsights(ctx.Configuration.GetValue<string>("APPINSIGHTS_INSTRUMENTATIONKEY"));
        }
        else
        {
            b.AddConsole();
        }
    })
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<DiscordBotService>();
    });

こんな感じで変更して Azure に差異デプロイして、こんな風に話しかけると

f:id:okazuki:20191219200344p:plain

こんな感じに Application Insights にもログが出るようになりました。

f:id:okazuki:20191219200438p:plain

ソースコード

とりあえずそのまま GitHub に上げてます。

github.com


ARFoundation と UIWidgets を使う

$
0
0

ARFoundation を使うと Unity で iOS/Android 両対応の AR アプリが簡単に作れるということで注目が高そうな機能です。

docs.unity3d.com

AR の世界をタップしたとき何か当たったかどうかを判定するためには ARRaycastManagerRaycastすれば OK です。こんな感じです。タップした場所に適当な大きさのキューブと綺麗に削除する機能を用意してます。

publicclass ARTapHandler : MonoBehaviour
{
    public ARRaycastManager raycastManager;
    privatereadonly List<GameObject> _cubes = new List<GameObject>();

    // Update is called once per framevoid Update()
    {
        if (Input.touchCount == 1)
        {
            var touch = Input.GetTouch(0);
            if (touch.phase != TouchPhase.Began)
            {
                return;
            }

            var hits = new List<ARRaycastHit>();
            if (!raycastManager.Raycast(touch.position, hits))
            {
                return;
            }

            var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            cube.transform.position = hits[0].pose.position;
            cube.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
            _cubes.Add(cube);
        }
    }

    publicvoid ClearCubes()
    {
        foreach (var cube in _cubes)
        {
            GameObject.Destroy(cube);
        }

        _cubes.Clear();
    }
}

UIWidgets と使うと…

この画面に UIWidgets を適用してみると、まぁ当たり前ですが UI 部品をタップしても上記ロジックでは AR の世界に対する処理も走ってしまいます。 ぱっと思いつくのは、Raycast をする前にタップした場所が UI の置いてある場所なのか、そうじゃないのかを判定しないといけません。 ということで前回の UIWidgets で透明なところは後ろ側にイベントを飛ばすという処理に書いてあった、タップした場所が透明かどうかを判定するロジックを組み込んで、これを実現してみようと思います。

blog.okazuki.jp

ということで、上記コードに処理を追加してやりました。

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.UIWidgets.engine;
using UnityEngine;
using UnityEngine.XR.ARFoundation;

namespace UnityAppTest.Ar
{
    publicclass ARTapHandler : MonoBehaviour
    {
        public UIWidgetsPanel uiWidgetsPanel;
        public ARRaycastManager raycastManager;
        privatereadonly List<GameObject> _cubes = new List<GameObject>();

        // Update is called once per framevoid Update()
        {
            if (Input.touchCount == 1)
            {
                var touch = Input.GetTouch(0);
                if (touch.phase != TouchPhase.Began)
                {
                    return;
                }

                // ここから
                var rTex = uiWidgetsPanel.texture as RenderTexture;
                if (rTex != null)
                {
                    var tex = new Texture2D(rTex.width, rTex.height, TextureFormat.RGBA32, false);
                    RenderTexture.active = rTex;
                    tex.ReadPixels(new Rect(0, 0, rTex.width, rTex.height), 0, 0);
                    tex.Apply();
                    Vector2 local;
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(uiWidgetsPanel.rectTransform, touch.position, null, out local);

                    var rect = uiWidgetsPanel.rectTransform.rect;

                    local.x += uiWidgetsPanel.rectTransform.pivot.x * rect.width;
                    local.y += uiWidgetsPanel.rectTransform.pivot.y * rect.height;
                    var uvRect = uiWidgetsPanel.uvRect;
                    var u = local.x / rect.width * uvRect.width + uvRect.x;
                    var v = local.y / rect.height * uvRect.height + uvRect.y;
                    Debug.Log("alpha = " + tex.GetPixelBilinear(u, v).a);
                    if (tex.GetPixelBilinear(u, v).a != 0.0f)
                    {
                        Debug.Log("UI world!!");
                        return;
                    }
                }
                // ここまで

                Debug.Log("AR world!!");
                var hits = new List<ARRaycastHit>();
                if (!raycastManager.Raycast(touch.position, hits))
                {
                    return;
                }

                var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.position = hits[0].pose.position;
                cube.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
                _cubes.Add(cube);
            }
        }

        publicvoid ClearCubes()
        {
            foreach (var cube in _cubes)
            {
                GameObject.Destroy(cube);
            }

            _cubes.Clear();
        }
    }
}

コメントで、ここから、ここまでとなってる部分を何かしら共通部品のメソッドに括りだせば捗りそうですね。 とりあえず先日作った透明で Unity の Skybox をうつしてたやつを ARFoundation に移植して上記スクリプトを試してみました。背景がオレンジの部分が ARFoundation で認識された平面になります。透明部分をタップするとちゃんと Cube が作られて、透明じゃない UIWidgets の UI 部分をタップすると Cube を作る処理がされないことがわかります。

因みに画面左下のバツボタンには上記の ClearCubes を割り当ててます。

f:id:okazuki:20191220171555g:plain

Uno Platform で MainPage.xaml を開いたときにデザイナー(とインテリセンス)が出ないときの対象方法

$
0
0

デザイナーがエラーになるだけならいいんですが、たまにデザイナー自体無いケースがあります。

f:id:okazuki:20191222110849p:plain

上の画像では UWP プロジェクトを選択している状態で MainPage.xaml を開いているのにデザイナーが出ません。 一度ファイルを閉じて UWP プロジェクトをビルドして再度開いたり、Visual Studio を再起動したりするとうまくいくこともあるっぽいのですが、まぁ世の中上手くいきませんよね。

f:id:okazuki:20191222111049p:plain

緑の波線が出てインテリセンスが効くようになったらもう一歩です。MainPage.xaml を閉じて MainPage.xaml の右クリックメニューから「ファイルを開くアプリケーションの選択」で

f:id:okazuki:20191222111142p:plain

下記のように「XAMLデザイナー」を選択します。

f:id:okazuki:20191222111847p:plain

XAML デザイナーで開くと、先ほどまではなかった App20.Droidというプロジェクト名の選択のドロップダウンがあります。

f:id:okazuki:20191222111954p:plain

これを UWP のプロジェクトに選択しなおします。そして MainPage.xaml を一度閉じて開くとデザイナーさんおかえりなさい!!

f:id:okazuki:20191222112108p:plain

まとめ

大体こちらの FAQ ページにある Does intellisense work in XAML editor?の内容になります。

platform.uno

それでは!

Uno Platform でプラットフォームによって実装されていない機能を実行時に判別する

$
0
0

さて、Uno Platform ですが UWP の API を気合で Android/iOS 向けに提供しているのですが、全部の API が提供されているわけではないです。 UI まわりは、かなり動きますがそれ以外は結構 NotImplementedException が出てしまいます。

例えば以下のようにボタンを押したらファイルを選択するダイアログを出してファイル名をダイアログで出すような機能を実装したとします。

var picker = new FileOpenPicker();
picker.FileTypeFilter.Add(".png");
picker.FileTypeFilter.Add(".jpg");

var file = await picker.PickSingleFileAsync();
if (file == null)
{
    await new MessageDialog("Canceled").ShowAsync();
    return;
}

await new MessageDialog(file.Name).ShowAsync();

FileOpenPicker は、実装されてないクラスのうちの一つなので UWP 以外では現時点では例外になります。でも UWP では FileOpenPicker を使いたい、Android/iOS などの対応していないプラットフォームではそれなりに動かしたいという場合はどうするのかというのですが、#ifディレクティブや partial classでプラットフォームごとの処理を書くという方法もありますが、機能自体が提供されているかを実行時に判定する方法もあります。

Windows.Foundation.Metadata.ApiInformationIsTypePresentでクラスが提供されているかどうか、IsMethodPresentでメソッドが提供されているかどうか確認できます。例えばさっきの処理に追加するとこんな感じです。

if (ApiInformation.IsTypePresent("Windows.Storage.Pickers.FileOpenPicker"))
{
    var picker = new FileOpenPicker();
    picker.FileTypeFilter.Add(".png");
    picker.FileTypeFilter.Add(".jpg");

    var file = await picker.PickSingleFileAsync();
    if (file == null)
    {
        await new MessageDialog("Canceled").ShowAsync();
        return;
    }

    await new MessageDialog(file.Name).ShowAsync();
}
else
{
    await new MessageDialog("FileOpenPicker isn't provided at this platfrom.").ShowAsync();
}

UWP で実行するとダイアログが出て

f:id:okazuki:20191226233958p:plain

例えば iOS で起動すると、こんな感じでエラーではなくサポートされていないことを判定して個別の処理が実行可能です。

f:id:okazuki:20191226234151p:plain

まとめ

ケースバイケースだけど、こういう方法もあるよって感じで

Uno Platform でノッチとかに対応したい(セーフエリア)

$
0
0

iOS エミュレーターで動かすとノッチに食い込むテキストやボタンに悩まされる今日この頃。Uno Platform で対応する場合には Uno.UI.Toolkit.VisibleBoundsPaddingクラスの PaddingMaskプロパティでセーフエリアに入るように Padding を自動で調整することが出来ます。

例えば、ページ内のルートのレイアウトパネルに以下のように指定します。

<Pagex:Class="App22.MainPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="using:App22"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:toolkit="using:Uno.UI.Toolkit"mc:Ignorable="d"><RelativePanel toolkit:VisibleBoundsPadding.PaddingMask="All"Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><TextBlockx:Name="textBlockLabel"Margin="20"FontSize="30"Text="Hello, world!" /><Buttonx:Name="buttonOk"Margin="20,0"Click="ButtonOk_Click"Content="OK"FontSize="20"RelativePanel.Below="textBlockLabel" /></RelativePanel></Page>

これで上下左右にいい感じの余白が出来て画面内にコンテンツがおさまります。

指定あり

f:id:okazuki:20191226235328p:plain

指定なし

f:id:okazuki:20191226235519p:plain

Uno Platform の WebAssembly プロジェクトを Azure にデプロイする時にエラーになる

$
0
0

Visual Studio 2019 の発行ウィザードから Azure にデプロイしようとすると以下のようなエラーが出力ウィンドウに出るんですよね。

C:\Users\user name\.nuget\packages\uno.wasm.bootstrap\1.0.10\build\Uno.Wasm.Bootstrap.targets(125,5): Error : Failed to create directory \\?\C:\App1\App1.Wasm\bin\Release\netstandard2.0\dist\managed
C:\Users\user name\.nuget\packages\uno.wasm.bootstrap\1.0.10\build\Uno.Wasm.Bootstrap.targets(125,5): Error : System.ArgumentException: Illegal characters in path.
   at System.Security.Permissions.FileIOPermission.EmulateFileIOPermissionChecks(String fullPath)
   at System.IO.Directory.InternalCreateDirectoryHelper(String path, Boolean checkHost)
   at System.IO.Directory.CreateDirectory(String path)
   at Uno.Wasm.Bootstrap.ShellTask_v1be21f07f93cd3a369c95001065dbda54db9dca8.DirectoryCreateDirectory(String directory)
   at Uno.Wasm.Bootstrap.ShellTask_v1be21f07f93cd3a369c95001065dbda54db9dca8.CreateDist()
   at Uno.Wasm.Bootstrap.ShellTask_v1be21f07f93cd3a369c95001065dbda54db9dca8.Execute()

結論としては、WebAssembly プロジェクトのプロジェクトファイルの一番上にある PropertyGroup タグの下あたりに以下の定義を追加すれば回避できました。

<WasmShellEnableLongPathSupport>false</WasmShellEnableLongPathSupport>

Issue を立てたら丁寧に対応してもらえました。

github.com

Issue のタグに 2.1 というのがついてるので Uno Platform 2.1 がリリースされるときには、この回避策はいらない子になってると思います。

Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>