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

C++/WinRT で Windows Runtime Component を作って C# から呼ぶまで

$
0
0

ちょっと迷ったのでメモしておきます。

C+/WinRT自体についてはこちら

docs.microsoft.com

上のドキュメントの、このページの部分に関連してます。

docs.microsoft.com

docs.microsoft.com

Step 1: Windows Runtime Component プロジェクトの作成と idl の定義

Windows Runtime Component (C++/WinRT) のプロジェクトを作成します。今回は名前は MyComponents にしました。 Class.idl` があるのですが、これは消します。

そして Person.idlを作成するのと Person.hPerson.cppも作成します。 Person.idlに Person クラスを定義します。

namespace MyComponents
{
    runtimeclass Person : Windows.UI.Xaml.Data.INotifyPropertyChanged
    {
        Person();
        String Name;
    }
}

インテリセンスとかないので辛いです。

Step 2: Person クラスの作成

idl を書いてビルドしてプロジェクトのすべてのファイルを表示すると Person.g.hPerson.g.cppが生成されています。

f:id:okazuki:20190920135105p:plain

ここには winrt::MyComponents::implementation::Person_baseクラスと、その別名の winrt::MyComponents::implementation::PersonTと、winrt::MyComponents::factory_implementation::PersonTが定義されています。この PersonTという名前の 2 つのクラスを継承して Personクラスを自分のコードに追加します。

Person.hは以下のような感じになります。

#pragma once#include "Person.g.h"// 生成されたヘッダーを includenamespace winrt::MyComponents::implementation
{
    struct Person : PersonT<Person>
    {
    };
}

namespace winrt::MyComponents::factory_implementation
{
    struct Person : PersonT<Person, implementation::Person>
    {
    };
}

PersonTとか Personとか同じ名前が出てきますが、これは名前空間が違うので気を付けてみましょう。

Step 3: Person クラスの実装

Person クラスの中を作っていきます。idl ファイルに定義していた Nameプロパティと INotifyPropertyChangedの実装をします。Person.hに、ここらへんを追加していきましょう。

#pragma once#include "Person.g.h"// 生成されたヘッダーを includenamespace winrt::MyComponents::implementation
{
    struct Person : PersonT<Person>
    {
        Person() = default;
        winrt::hstring Name() const;
        void Name(winrt::hstring const& name);

        winrt::event_token PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
        void PropertyChanged(winrt::event_token const& token);

    private:
        winrt::hstring _name;
        winrt::event<winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler> _propertyChanged;
    };
}

namespace winrt::MyComponents::factory_implementation
{
    struct Person : PersonT<Person, implementation::Person>
    {
    };
}

Person.cppに実装を書いていきます。頭にお決まりの include を書いて namespaceを定義したらヘッダーに戻って実装したいメソッドで Ctrl + .で以下のように、いい感じにひな型が作られます。

#include "pch.h"#include "Person.h"#include "Person.g.cpp"// 自動生成された cpp を includenamespace winrt::MyComponents::implementation
{
    winrt::hstring Person::Name() const
    {
        return winrt::hstring();
    }
    void Person::Name(winrt::hstring const& name)
    {
    }
    winrt::event_token Person::PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return winrt::event_token();
    }
    void Person::PropertyChanged(winrt::event_token const& token)
    {
    }
}

中身を実装してこんな感じ。

#include "pch.h"#include "Person.h"#include "Person.g.cpp"// 自動生成された cpp を includenamespace winrt::MyComponents::implementation
{
    winrt::hstring Person::Name() const
    {
        return _name;
    }
    void Person::Name(winrt::hstring const& name)
    {
        _name = name;
        _propertyChanged(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs(L"Name"));
    }
    winrt::event_token Person::PropertyChanged(winrt::Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
    {
        return _propertyChanged.add(handler);
    }
    void Person::PropertyChanged(winrt::event_token const& token)
    {
        _propertyChanged.remove(token);
    }
}

Step 4: C# から使う

C# の UWP アプリを作ります。ここでは CSharpUWPAppにしました。 CSharpUWPAppから MyComponentsに参照設定を追加します。ここらへんは普通の C# のクラスライブラリ使うときと一緒ですね。という`か、ちゃんと C++/WinRT で Windows ランタイムコンポーネントが定義出来ていれば C# から使うのは普通の C# のクラスを使うときと変わりありません。

ということで MainPage.xaml.csPerson` クラスをプロパティに持たせて…

using MyComponents;
using Windows.UI.Xaml.Controls;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409namespace CSharpUWPApp
{
    /// <summary>/// An empty page that can be used on its own or navigated to within a Frame./// </summary>publicsealedpartialclass MainPage : Page
    {
        private Person Person { get; } = new Person { Name = "Tanaka" };
        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}

MainPage.xamlで適当にバインドしましょう。

<Pagex:Class="CSharpUWPApp.MainPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:CSharpUWPApp"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><StackPanel><TextBox Text="{x:Bind Person.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /><TextBlock Text="{x:Bind Person.Name, Mode=OneWay}" /></StackPanel></Page>

実行するとちゃんと使えています。

f:id:okazuki:20190920151918g:plain

まとめ

ハイパフォーマンスで美しい Windows Runtime Component を C++/WinRT で!!…作れるようになる道のりは自分にとってははるか長いように見える。 がんばろっと。

ソースコードはこちら。

github.com


Go 言語で学ぶ AWS Lambda の学習用環境構築メモ

$
0
0

技術書典 7 で Goで学ぶAWS Lambda を見て BOOTH で購入しました。

booth.pm

さくっとみてみると macOS 上に Python, aws cli, go, docker などを入れてます…

う~ん、どれも普段使わない関係のものなので PC に直接入れたくない…!?ということで最近試してみてる Visual Studio Code のリモート開発で環境を作ってみようと思います。

docker on docker しないといけなくなったら、その時にまた頑張ろう。ということでとりあえず AWS の CLI と go が入ってる状態を目指します。

ベースとなるテンプレート

単一コンテナーだけで済まなくなるケースも考えて docker-compose を使っていて AWS CLI に必要な Python が入ってるものということで、Python 3 & PostgreSQLを選びました。

f:id:okazuki:20190923144657p:plain

.devcontainerフォルダーに Dockerfileがあるので Python 以外にも Go 言語も入れます。元ネタは Go 言語の公式イメージの Dockerfile

github.com

これとマージしつつ AWS CLI も pip でインストールするようにして以下のような Dockerfile になりました。 無駄はありそうなものの、まぁ動くのでとりあえずよしとしよう。

LearningAWSLambda/Dockerfile at 2079fb87aca270eba075fbd509ab76c3e2a042b1 · runceel/LearningAWSLambda · GitHub

.devcontainer/docker-compose.ymlは DB 環境がいらないのですっきりさせましょう。

version: '3'

services:
  app:
    build: 
      context: ..
      dockerfile: .devcontainer/Dockerfile

    volumes:
      - ..:/go/src
    
    command: sleep infinity

.devcontainer/docker-compose.secret.ymlファイルを作って AIM の ID と KEY を環境変数に入れておきます。

version: '3'

services: 
  app:
    environment: 
      - AWS_ACCESS_KEY_ID=xxxxxx
      - AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxx
      - AWS_DEFAULT_REGION=ap-northeast-1
      - AWS_DEFAULT_OUTPUT=json

この .devcontainer/docker-compose.secret.yml.gitignoreでリポジトリーには上がらないようにしておきました。

.devcontainer/devcontainer.jsonは、とりあえず /go/srcを作業用のフォルダーにして Go と AWS の拡張機能を入れておく感じにしておきました

{"name": "Learning for AWS Lambda using Golang",
    "dockerComposeFile": ["docker-compose.yml",
        "docker-compose.secret.yml"
    ],
    "service": "app",
    "workspaceFolder": "/go/src",
    "extensions": ["ms-vscode.go",
        "amazonwebservices.aws-toolkit-vscode"
    ]}

この状態でリモート開発機能でオープンすると…

f:id:okazuki:20190923155626p:plain

goコマンドも叩けて awsコマンドも叩けて aws s3 lsもエラーが出なかったので、ちゃんと S3 作れば結果が返ってくると信じたい。

適当に Go の Hello world を書いて F5 を押してみたらちゃんとデバッグもできた。

f:id:okazuki:20190923155904p:plain

そろそろ、本を読み始めてもいいかな??ということでゲームしよう。

時刻がずれてた

コンテナー上のイメージの時刻がずれてて aws s3 lsが失敗してたので、以下のページを参考にしました。

qiita.com

私の場合は最初からチェックが入ってたので、一度オフにして適用したあとに、またオンにしたら時刻がちゃんとなりました。

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

$
0
0

今回の更新はアップデートをすると 100% ビルドエラー or ランタイムエラーになります。注意して更新してください。

リリースノートはこちら。

Release v3.0.0 · runceel/Livet · GitHub

NuGet パッケージはこちら

.NET Framework 用

.NET Core 用

依存先の Microsoft.Xaml.Behaviors.Wpf が .NET Core 対応をしていないため、こちらのパッケージも Preview にしています。 ソースコードは、.NET Framework 版と同じものからビルドしているので使っていただいても大丈夫です。何か問題があれば Issue にあげてください。

変更点

Blend SDK から Microsoft.Xaml.Behaviors.Wpf への移行

Blend SDK (System.Windows.Interactivity などの Behavior のライブラリ) が Visual Studio 2019 以降提供されなくなったのを受けて、後継のライブラリの Microsoft.Xaml.Behaviors.Wpf に移行しました。

これにともない、Livet で提供している大量の Behavior / Trigger / Action の全てが過去のバージョンと基本クラスが変わっているためビルドエラーになります。 C# ソースコード内の名前空間の機械的な置換と、XAML ソースコード内の XML 名前空間のほぼほぼ機械的な置換で移行が可能になりますが、アップデート時には注意をしてください。

詳細については、下記ブログ記事を参照してください。

devblogs.microsoft.com

Livet の拡張機能の Visual Studio 2017 のサポートを辞めました

最新の Livet の拡張機能を利用する場合は Visual Studio 2019 をご利用ください。2017 で利用する場合は、こちらから vsix をダウンロードして過去のバージョンをご利用ください。

Release v2.2.0 · runceel/Livet · GitHub

Liver 3.0.3 をリリースしました。Prism + Livet (一部) のようなシナリオをサポート

$
0
0

リリースしました。リリースノートは以下になります。

github.com

ポイント

パッケージを分割しました。

今まで

LivetCask

全部入りパッケージ

LivetExtensions

フォルダーダイアログ

今回

LivetCask

Livet とは切っても切れないクラス群(ViewModel、Messenger 関連、コレクション)と、以下の各種パッケージへの依存関係。

LivetCask.Behaviors

Livet の便利 Behavior。バインドできないプロパティをバインドするようなものや LivetCallMethodAction、DataContextDisposeAction、WindowCloseCancelBehavior など

LivetCask.Converters

WPF 関連の enum と bool を相互変換するコンバーター群

LivetCask.EventListener

PropertyChangedListener や CollectionChangedEventListener などのイベントハンドリング系クラス

LivetExtensions

フォルダーダイアログ

出来るようになること

Prism + LivetCask.Behaviors などのパッケージの組み合わせ

例えば Prism のプロジェクトに Livet.Converters パッケージと Livet.Behaviors パッケージを追加して以下のような感じに ViewModel をします。

using Prism.Mvvm;

namespace HelloWorld.ViewModels
{
    publicclass ViewAViewModel : BindableBase
    {
        privatestring _message;
        publicstring Message
        {
            get { return _message; }
            set { SetProperty(ref _message, value); }
        }

        privatebool _status;
        publicbool Status
        {
            get { return _status; }
            set { SetProperty(ref _status, value); }
        }

        public ViewAViewModel()
        {
            Message = "View A from your Prism Module";
        }

        publicvoid Switch()
        {
            Status = !Status;
        }
    }
}

そして XAML で Livet の機能を使います。

<UserControl x:Class="HelloWorld.Views.ViewA"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:HelloWorld.Views"xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors"xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="300"xmlns:prism="http://prismlibrary.com/"prism:ViewModelLocator.AutoWireViewModel="True"><UserControl.Resources><l:VisibilityAndBooleanConverter x:Key="VisibilityAndBooleanConverter"ConvertWhenFalse="Collapsed" /></UserControl.Resources><StackPanel><TextBlock Text="{Binding Message}"HorizontalAlignment="Center" /><Button Content="Switch"><behaviors:Interaction.Triggers><behaviors:EventTrigger EventName="Click"><l:LivetCallMethodAction MethodTarget="{Binding}"MethodName="Switch" /></behaviors:EventTrigger></behaviors:Interaction.Triggers></Button><Border Background="Red"Width="100"Height="100"Visibility="{Binding Status, Converter={StaticResource VisibilityAndBooleanConverter}}"/></StackPanel></UserControl>

LivetCallMethodActionVisibilityAndBooleanConverterを使ってます。 こんな感じで動いてます。

f:id:okazuki:20190928123332g:plain

まとめ

Converter とか Behavior とか Listener 系は Livet じゃないプロジェクトでも、あると便利ですので是非使ってみてね!

新しい csproj 形式で NuGet パッケージに外部 dll を含める方法

$
0
0

Livet を新しい csproj 形式に移行した後に苦労したのが、Windows API Code Pack の dll を LivetExtensions のパッケージに含めたいというものでした。 わかってしまえば簡単で、dll をプロジェクトに含めて Pack="true" PackagePath="lib\net452"の属性でパッケージに含めることと、含める先のパスを指定する感じでした。

公式ドキュメント内での記述はみつけられませんでしたが、以下のドキュメント内でライセンスファイルを含めるという部分で同じようなことをしてます。

docs.microsoft.com

知ったきっかけ

Prism.Wpf が Blend SDK が手に入りにくくなった昨今、どうやってるのか確認したら、この方法でやってました。該当部分はここ。

Prism/Prism.Wpf.csproj at fdf163566601057f6d3c2c47b08f462de62fec48 · PrismLibrary/Prism · GitHub

ということで、Livet でも同じように対応しました。

Livet/LivetCask.Extensions.csproj at ddcc651b6a8c677ddf90b9af837107e07a07e613 · runceel/Livet · GitHub

<ItemGroup><Content Include="..\ReferenceAssemblies\Microsoft.WindowsAPICodePack.dll"Pack="true"PackagePath="lib\net452" /><Content Include="..\ReferenceAssemblies\Microsoft.WindowsAPICodePack.Shell.dll"Pack="true"PackagePath="lib\net452"/><Content Include="..\ReferenceAssemblies\Microsoft.WindowsAPICodePack.dll"Pack="true"PackagePath="lib\netcoreapp3.0" /><Content Include="..\ReferenceAssemblies\Microsoft.WindowsAPICodePack.Shell.dll"Pack="true"PackagePath="lib\netcoreapp3.0"/></ItemGroup>

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

$
0
0

今朝、パッケージ分割版をリリースした Livet ですが Messaging 機能を本体から分離していなかったので分離したバージョンをリリースしました。

バージョン番号が 3.0.x から 3.1.0 に上がったのは一部非互換がある可能性のある変更があるからです。

変更点

TransitionMessageTransitionViewModelプロパティの型を Livet.ViewModelから INofityPropertyChangedに変更しました。

通常の使い方では無いと思いますが TransitionMessageTransitionViewModelを参照して、Livet.ViewModelであることを前提にしたコードがあればキャストするなどの修正が必要になります。

うれしいこと

Livet の Messaging 機能には、ほかの MVVM フレームワークにはない View 起点のメッセージ機能があります。

GitHub - runceel/Livet: WPF MVVM Infrastructure.

これを Prism のプロジェクトに LivetCask.Messaging パッケージを入れると以下のように使うことが出来ます。

f:id:okazuki:20190928171055p:plain

まとめ

これで、いったん更新は落ち着きます。

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

$
0
0

StatefulModel も MVVM パターンでアプリケーションを作るときに便利なクラスが入ってるので Livet に取り込んでいいかと作成者の尾上さんに確認したところ快諾していただいたので取り込みました。

LivetCask.StatefulModel パッケージが追加されています。

このパッケージは従来の LivetCask のコレクション系のクラスと名前が同じだったりするのでパッケージを整理してコレクション部分だけを StatefulModel に置き換えられるように整理しました。

詳細は以下のリリースノートで!

github.com

MFC の MDI アプリでタブに表示される名前を変えたい

$
0
0

このタブのところの文字を変えたい。

f:id:okazuki:20190930162603p:plain

やり方は簡単でアプリのドキュメントクラスの プロジェクト名Docクラスのインスタンスに SetTitleメソッドを呼んでやればいい。

あんまりしないと思うけど プロジェクト名Docクラスの OnNewDocumentで以下のような処理を書くと…

BOOL CMFCApplication6Doc::OnNewDocument()
{
    if (!CDocument::OnNewDocument())
        return FALSE;

    // TODO: add reinitialization code here// (SDI documents will reuse this document)
    SetTitle(L"オレオレ!!俺だけど!");
    return TRUE;
}

こんな感じになる。

f:id:okazuki:20190930162832p:plain

もっときめ細やかな制御をしたかったりしたら、CMultiDocTemplateを継承して SetDefaultTitleメソッドをオーバーライドして、その中で CDocument::SetTitleしてやる感じになりそう。

作成した CMultiDocTemplateを継承したクラスは、プロジェクト名Appクラスの InitInstanceメソッド内で CMultiDocTemplateを生成している部分を探して置き換えてやることで有効になる。


MFC の MDI アプリで起動時に新しいドキュメントを開かないようにする

$
0
0

プロジェクト名Appクラスの InitInstanceメソッドで以下の部分を…

CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

こんな感じに編集する。

CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;

Vue CLI 3.x で生成した Vue.js + TypeScript を VS Code でデバッグするときの注意点 for Windows

$
0
0

かなりレアなケースだと思うので完全に自分への備忘録です。

Vue.js を VS Code でデバッグ出来るのは周知?の通り。

jp.vuejs.org

ただ、私の環境ではブレークポイントに止まらなくて困ったなぁとなってました。TypeScript 使ってるからか??と思ったのですが原因は別にありました。

シンボリックリンクのフォルダーで開発してるとダメ

パスが長いとダルイので C:\Reposをドキュメントフォルダーの下の Reposのシンボリックリンクとして作っておいて、普段は C:\Reposでアクセスできるようにしてました。 VS Code でも code C:\Repos\hogehogeみたいにして開いてました。

この状態で上記のデバッグの手順を踏むと Chrome はシンボリックリンクではないドキュメントフォルダーの下に実ファイルがあると思っていて(webpack:///C:\users\username\documents\repos\xxxxx みたいなパスになってた)、VS Code の中ではC:\Reposにあると思ってる感じになってしまい、当然ファイルの場所が違うのでブレークポイントを VS Code で指定しても動いてくれませんでした。

まとめ

シンボリックリンクのフォルダーではデバッグできないので、デバッグが必要なときは実フォルダー開きましょう。

凄い悩んだけど、シンボリックリンク使ってる人とか Windows では少数派なせいか情報がなかったので一応メモ。

Vue CLI 3 で作った TypeScript のプロジェクトを IE11 対応しよう

$
0
0

npm run buildしたものを IE11 で開くと Vue Router とかを有効にするだけで Promise がないって言われる…

f:id:okazuki:20191007122843p:plain

ということで以下のページを参考にやってみた。

qiita.com

個別 Polyfill はつらいのでざくっとやっていきます。 プロジェクトを作るときは TypeScript, babel, Vue Routerあたりを有効にして作りました。

babel.config.jsを以下のように変更。

module.exports = {
  presets: [['@vue/app',
      {
        useBuiltIns: 'entry'}]],
}

tsconfig.jsonlibes2017を追加

"lib": ["esnext",
  "dom",
  "dom.iterable",
  "scripthost",
  "es2017" // これね]

src/main.tsに以下の import を追加

import'@babel/polyfill';

そして、npm run buildして IE11 で開くと…

f:id:okazuki:20191007123529g:plain

動いたね!

Azure の AI 系サービスが Azure 無料アカウント作ると 1 年無料で使える枠がもらえるようになってる!

$
0
0

無料アカウント作ったときに無料で使える枠に AI 系サービスの Cognitive Services が含まれてます。

azure.microsoft.com

もともとフリープランがあったじゃん?って思ってたのですがチェックしてみると S0 とか S1 tier と書いてるじゃないですか。普通のはフリープラン系の枠は F で始まる名前なので、普通はお金かかるものが 1 年無料で使えるっぽい?

これは、いい感じ。

GitHub Actions for App Service を使ってサーバーサイド Blazor をデプロイしてみよう

$
0
0

8 月の時点でこういうブログ出てたのね!対応早い。

azure.github.io

ということで試してみようと思います。

プログラムの準備

サーバーサイド Blazor を試してみようと思うので dotnet new blazorserver -o GitHubActoinsBlazorと打ち込んで適当にプロジェクトを作ります。 出来たら VS Code で開きます。

f:id:okazuki:20191009153740p:plain

とりあえず git のリポジトリーにします。

f:id:okazuki:20191009153914p:plain

Add .gitignoreすると

f:id:okazuki:20191009154022p:plain

ひな型選んで追加できるのも便利。

f:id:okazuki:20191009154055p:plain

適当にコミットして GitHub にリポジトリー作って git remote addして git push -u origin master相当のことを VS Code からやって準備完了!

デプロイ用の資格情報の取得

適当に Azure に Web App を作って発行プロファイルの取得を選んでファイルをダウンロードします。

f:id:okazuki:20191009155800p:plain

GitHub のリポジトリーに移動して Settings の Secrets で適当な名前でダウンロードした発行プロファイルのファイルの中身を張り付けてシークレットを作ります。

f:id:okazuki:20191009160134p:plain

YAML の準備

では GitHub のリポジトリーの Actions から Set up a workflow yourselfを選んで main.yml を以下のような感じで作ります。

name: Build and deploy to Azure

on:[push]jobs:build:name: build using
    runs-on: ubuntu-latest
    steps:- uses: actions/checkout@master

      - name: Setup .NET Core
        uses: actions/setup-dotnet@v1
        with:dotnet-version: 3.0.100
    
      - name: Build with dotnet
        run: dotnet build --configuration Release
      - name: dotnet publish
        run: dotnet publish -c Release -r win-x86 --self-contained -o myapp
      
      - name: deploy to Azure
        uses: azure/webapps-deploy@v1
        with:app-name: myappservicename
          publish-profile: ${{ secrets.azureWebAppPublishProfile }}
          package:'./myapp'

作ると…なんか動き始めた

f:id:okazuki:20191009161038p:plain

全部成功したので Web App のページを開いてみると…、ちゃんと動いた!!

f:id:okazuki:20191009161212p:plain

試しにちょっと更新してみよう。

凄い勢いで増える気持ちいいカウンターになるようにコードを変更して…

f:id:okazuki:20191009162036p:plain

コミットしてプッシュするとビルドが走り始めました

f:id:okazuki:20191009162219p:plain

デプロイまで完了したのでアプリを見てみると…ちゃんと更新されてますね!

f:id:okazuki:20191009162428p:plain

まとめ

GitHub Actions for App Service 使えば簡単に Azure App Service にデプロイできそうです。 最初に紹介したブログには node.js, Java など何故か .NET 以外の例がふんだんに載ってるので、ここでは最新 .NET Core 3.0 のサーバーサイド Blazor を試してみました!

あとは、サーバーサイド Blazor を Azure に本格的にデプロイするなら SignalR Service 使ったりしたいとかあるけど、まぁそれはコードちょっと変えて構成ちょっと足すだけなので今回はいいかな。

WPF on .NET Core 3.0 で XAML Islands してみよう

$
0
0

しばらく目を離してると手順がガラッと変わっててびっくりしてる今日この頃。

docs.microsoft.com

WPF on .NET Core 3.0 で XAML Islands を試してみたいと思います。

プロジェクトの構成

前は WPF プロジェクトにライブラリ追加して…という感じだったのですが今は UWP プロジェクトを追加して…となってました。びっくり。 ということで一般的な XAML Islands を WPF で使う場合は、以下の 3 プロジェクト構成になることが多い感じです。

  • UWPApp (普通の UWP アプリプロジェクト)
  • UserControlLibrary (UWP のクラスライブラリ)
  • WPFApp (普通の WPF アプリプロジェクト)

UWPApp プロジェクトは XAML Islands を WPF でホストするための特殊な XamlApplication クラスをアプリケーションプロジェクトに持つ UWP アプリです。WPFApp から参照して使います。

UserControlLibrary は UWP のクラスライブラリプロジェクトで、UWP のユーザーコントロールを作って WPF アプリに追加したい場合は、ここに定義します。

WPFApp プロジェクトは、UWPApp と UserControlLibrary プロジェクトを参照していて Microsoft.Toolkit.Wpf.UI.Controlsパッケージを参照に追加してます。

やってみよう

空のソリューションから始めてみます。

UWP プロジェクトの作成

まず、普通の UWP アプリケーションを作成します。選択する Windows のバージョンは 1903 です。XAML Islands の現在の対応バージョンです。ここら辺は来年出るという Windows UI Library 3.0 が出ると Creators Update 以降に対応になりますが今のところ 1903 です。

f:id:okazuki:20191010195109p:plain

MainPage.xaml/MainPage.xaml.csは使わないので消します。

さて、ここから追加するライブラリーは基本的に何も言及していない場合は、最新のプレビューバージョンです。 UWP のプロジェクトに Microsoft.Toolkit.Win32.UI.XamlApplicationの NuGet パッケージを追加します。

追加したら App.xaml を以下のように書き換えます。

<xaml:XamlApplicationx:Class="UWPApp.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:xaml="using:Microsoft.Toolkit.Win32.UI.XamlHost"xmlns:local="using:UWPApp"></xaml:XamlApplication>

そして App.xaml.cs を以下のようにします。シンプルですね。

using Microsoft.Toolkit.Win32.UI.XamlHost;

namespace UWPApp
{
    sealedpartialclass App : XamlApplication
    {
        public App()
        {
            Initialize();
        }
    }
}

WPF プロジェクトの作成

.NET Core の WPF プロジェクトを作って Microsoft.Toolkit.Wpf.UI.Controlsの NuGet パッケージを追加します。 そして、UWP プロジェクトへの参照を WPF のプロジェクトに追加します。

f:id:okazuki:20191010195950p:plain

Configuration Manager (Visual Studio のツールバーの x86 とか書いてある部分のドロップダウンから開けたりします)で WPF のプロジェクトが Any CPU になっているものを x86 のときは x86、x64 のときは x64 になるように変更します。Debug と Release 両方で構成しておきましょう。

f:id:okazuki:20191010200310p:plain

コントロールを置いてみよう

InkCanvas などのラップ済みコントロールがある場合はそれをそのまま置いたりできます。無い場合は WindowsXamlHostクラスで InitialTypeNameプロパティに型名を指定して、ChildChangedイベントでコントロールのプロパティを設定します。 例えば、TextBox を指定したい場合はこんな感じ。

<Window x:Class="WpfApp.MainWindow"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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp"xmlns:xaml="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><xaml:WindowsXamlHost InitialTypeName="Windows.UI.Xaml.Controls.TextBox"ChildChanged="WindowsXamlHost_ChildChanged" /></Grid></Window>
using Microsoft.Toolkit.Wpf.UI.XamlHost;
using System;
using System.Windows;

namespace WpfApp
{
    /// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclass MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        privatevoid WindowsXamlHost_ChildChanged(object sender, EventArgs e)
        {
            var host = (WindowsXamlHost)sender;
            var textBox = host.Child as Windows.UI.Xaml.Controls.TextBox;
            if (textBox != null)
            {
                textBox.PlaceholderText = "xxxxxxx";
            }
        }
    }
}

これで実行すると、WPF には無い(何故無い)ウォータマークのついた TextBox が出来ます。

f:id:okazuki:20191010201459p:plain

こんなのは望んでない

俺は XAML で書きたいんだ!!InkCanvasとかは提供されてるということは、何か方法があるはずだ!ということでリポジトリで InkCanvasの実装を見ます。

github.com

ふむふむ、WindowsXamlHostBaseクラスを継承して作るのね…ということでこんな感じで

using System;
using System.Windows;
using UWP = Windows.UI.Xaml.Controls;

namespace WpfApp
{
    publicclass UwpTextBox : Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHostBase
    {
        publicstring PlaceholderText
        {
            get { return (string)GetValue(PlaceholderTextProperty); }
            set { SetValue(PlaceholderTextProperty, value); }
        }

        publicstaticreadonly DependencyProperty PlaceholderTextProperty =
            DependencyProperty.Register("PlaceholderText", typeof(string), typeof(UwpTextBox), new PropertyMetadata(null));

        publicstring Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        publicstaticreadonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(UwpTextBox), new PropertyMetadata(null));

        public UwpTextBox() : base(typeof(UWP.TextBox).FullName)
        {
        }

        protectedoverridevoid OnInitialized(EventArgs e)
        {
            // Bind dependency properties across controls// properties of FrameworkElement
            Bind(nameof(Style), StyleProperty, UWP.TextBox.StyleProperty);
            Bind(nameof(MaxHeight), MaxHeightProperty, UWP.TextBox.MaxHeightProperty);
            Bind(nameof(FlowDirection), FlowDirectionProperty, UWP.TextBox.FlowDirectionProperty);
            Bind(nameof(Margin), MarginProperty, UWP.TextBox.MarginProperty);
            Bind(nameof(HorizontalAlignment), HorizontalAlignmentProperty, UWP.TextBox.HorizontalAlignmentProperty);
            Bind(nameof(VerticalAlignment), VerticalAlignmentProperty, UWP.TextBox.VerticalAlignmentProperty);
            Bind(nameof(MinHeight), MinHeightProperty, UWP.TextBox.MinHeightProperty);
            Bind(nameof(Height), HeightProperty, UWP.TextBox.HeightProperty);
            Bind(nameof(MinWidth), MinWidthProperty, UWP.TextBox.MinWidthProperty);
            Bind(nameof(MaxWidth), MaxWidthProperty, UWP.TextBox.MaxWidthProperty);
            Bind(nameof(UseLayoutRounding), UseLayoutRoundingProperty, UWP.TextBox.UseLayoutRoundingProperty);
            Bind(nameof(Name), NameProperty, UWP.TextBox.NameProperty);
            Bind(nameof(Tag), TagProperty, UWP.TextBox.TagProperty);
            Bind(nameof(DataContext), DataContextProperty, UWP.TextBox.DataContextProperty);
            Bind(nameof(Width), WidthProperty, UWP.TextBox.WidthProperty);

            Bind(nameof(Text), TextProperty, UWP.TextBox.TextProperty);
            Bind(nameof(PlaceholderText), PlaceholderTextProperty, UWP.TextBox.PlaceholderTextProperty);

            base.OnInitialized(e);
        }
    }
}

そして、MainWindow.xaml で、これを使います。

<Window x:Class="WpfApp.MainWindow"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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><local:UwpTextBox PlaceholderText="xxxxxxx" /></Grid></Window>

実行すると、同じ結果ですけど XAML が健康的になりました。

f:id:okazuki:20191010202917p:plain

ユーザーコントロールを使いたい

UWP のクラスライブラリープロジェクト(ターゲットは 1903)をつくって、そこにユーザーコントロールを追加します。

プロジェクトをアンロードして、</Project>の前の行に以下の定義を追加します。

<PropertyGroup><EnableTypeInfoReflection>false</EnableTypeInfoReflection><EnableXBindDiagnostics>false</EnableXBindDiagnostics></PropertyGroup>

プロジェクトをロードして WPF と UWP のプロジェクトの両方に参照を追加します。

そして、ユーザーコントロールを追加して XAML を以下のようにします。

<UserControlx:Class="MyUserControls.MyUserControl1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:MyUserControls"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="400"><StackPanel><TextBox /><Button Content="Greet" /></StackPanel></UserControl>

WindowsXamlHostでさくっと WPF 上に置きます。

<Window x:Class="WpfApp.MainWindow"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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp"xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><xamlhost:WindowsXamlHost InitialTypeName="MyUserControls.MyUserControl1" /></Grid></Window>

おk

f:id:okazuki:20191010204820p:plain

これも XAML の平和のためにラッパ=を作ろうとしたらアプリが落ちました。残念。

ViewModel を共有したい

これは試行錯誤中ですが、動くのは出来ました。

WPF 側のプロジェクトに Prism.Core (stable 版の最新) を入れます。 そして、適当に ViewModel をこしらえます。

using Prism.Commands;
using Prism.Mvvm;

namespace WpfApp
{
    publicclass MainWindowViewModel : BindableBase
    {
        privatestring _input;
        publicstring Input
        {
            get { return _input; }
            set { SetProperty(ref _input, value); }
        }

        privatestring _output;
        publicstring Output
        {
            get { return _output; }
            set { SetProperty(ref _output, value); }
        }

        private DelegateCommand _convertCommand;
        public DelegateCommand ConvertCommand =>
            _convertCommand ?? (_convertCommand = new DelegateCommand(ExecuteConvertCommand, CanExecuteConvertCommand)
                .ObservesProperty(() => Input));

        privatevoid ExecuteConvertCommand()
        {
            Output = Input.ToUpper();
        }

        privatebool CanExecuteConvertCommand()
        {
            return !string.IsNullOrWhiteSpace(Input);
        }
    }
}

そして Window の DataContext に設定します。ついでに Output プロパティを WPF の TextBlock にバインドしておきます。

<Window x:Class="WpfApp.MainWindow"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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp"xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Window.DataContext><local:MainWindowViewModel /></Window.DataContext><StackPanel><xamlhost:WindowsXamlHost InitialTypeName="MyUserControls.MyUserControl1"ChildChanged="WindowsXamlHost_ChildChanged" /><TextBlock Text="{Binding Output}" /></StackPanel></Window>

WindowsXamlHost の ChildChanged イベントで DataContext を伝搬させます。

using Microsoft.Toolkit.Wpf.UI.XamlHost;
using MyUserControls;
using System;
using System.Windows;

namespace WpfApp
{
    /// <summary>/// Interaction logic for MainWindow.xaml/// </summary>publicpartialclass MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        privatevoid WindowsXamlHost_ChildChanged(object sender, EventArgs e)
        {
            var host = (WindowsXamlHost)sender;
            var control = host.Child as MyUserControl1;
            if (control != null)
            {
                control.DataContext = DataContext;
            }
        }
    }
}

そして UWP 側のユーザーコントロールで Binding します。(x:Bindじゃないのが残念な点)

<UserControlx:Class="MyUserControls.MyUserControl1"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:MyUserControls"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="400"><StackPanel><TextBox Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/><Button Content="Greet"Command="{Binding ConvertCommand}"/></StackPanel></UserControl>

OK 動いた。

f:id:okazuki:20191010210227p:plain

デプロイどうするの?

WPF のプロジェクトを publish するとエラー。

f:id:okazuki:20191010210931p:plain

Visual Studio の Configuration を Release にすると Visual Studio がフリーズ。(待てば戻ってくるのかな…?)

f:id:okazuki:20191010211024p:plain

とりあえず時間切れなのでデプロイなどについては後日調べてみることにします。

WPF on .NET Core 3.0 + XAML Islands で Windows UI Library を使おう

$
0
0

前回

blog.okazuki.jp

Windows UI Library を使おう

Windows UI Library(WinUI) は、次のメジャーアップデートから Windows の OS のバージョンアップに紐づいてアップデートされてきた UWP の UI 部分を OSS として切り離して開発するというポジションになるライブラリです。

今時点の v2 系は、最新の UWP コントロールを古い OS に向けてバックポートするためのライブラリって感じですが、今後は最新だろうがなんだろうか WinUI という感じになりそうです。

さて、その中のコントロールを .NET Core 3.0 の WPF で使ってみましょう。

プロジェクト作り

とりあえず前回と同じで、WPF、UWP、クラスライブラリ(UWP)のプロジェクトを作って下準備します。

下準備が終わったら Microsoft.UI.Xamlパッケージの最新のプレビュー版を UWP のプロジェクトに追加します。 追加したら App.xaml に WinUI 関連のリソースを読み込ませます。

<xaml:XamlApplicationx:Class="UWPHost.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:xaml="using:Microsoft.Toolkit.Win32.UI.XamlHost"xmlns:local="using:UWPHost"><Application.Resources><XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /><!-- これ --></Application.Resources></xaml:XamlApplication>

Windows UI ライブラリーの RatingControl を置いて…

<UserControlx:Class="WinUIControls.UwpControlsHost"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:WinUIControls"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:winui="using:Microsoft.UI.Xaml.Controls"mc:Ignorable="d"d:DesignHeight="300"d:DesignWidth="400"><Grid><winui:RatingControl /></Grid></UserControl>

MainWindow.xaml にこのユーザーコントロールを置くと…

<Window x:Class="WinUIWpf.MainWindow"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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WinUIWpf"xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><StackPanel><xamlhost:WindowsXamlHost InitialTypeName="WinUIControls.UwpControlsHost" /></StackPanel></Window>

アプリがクラッシュするようになりました!RatingControl を TextBox に置き換えると動くので WinUI が何か悪さをしているように見えます。 Visual Studio の出力ウィンドウにも、それらしいメッセージは出ない。

ということで直観に従って MSIX にパッケージングしてみようと思います。 Windows アプリケーション パッケージ プロジェクトを追加します。ターゲットなどは 1903 でつくります。

パッケージ対象のプロジェクトは WPF のプロジェクトです。

f:id:okazuki:20191015100251p:plain

そして、下記ドキュメントにあるとおり .NET Core 3.0 のアプリをパッケージ化するときに今のところ手動でプロジェクトファイルを書き換えないといけない部分を書き換えます。 パッケージプロジェクトの Package.appxmanifest の SplashScreen タグもドキュメントに従って書き換えます。

XAML アイランドを使用した WPF アプリでのカスタム UWP コントロールのホスト | Microsoft Docs

<uap:SplashScreen Image="Images\SplashScreen.scale-200.png" />

WPF プロジェクトファイルに以下の PropertyGroup タグを追加

<PropertyGroup><AssetTargetFallback>uap10.0.18362</AssetTargetFallback></PropertyGroup>

パッケージプロジェクトを実行すると…

出た!

f:id:okazuki:20191015100757p:plain

でも、MSIXに固めようとするとエラー。

f:id:okazuki:20191015101429p:plain

ということでロゴやらなんやらを Package.appxmanifest の Visual Assets のページでどかっと設定しました。

f:id:okazuki:20191015101554p:plain

そうすると、無事出来ました。

f:id:okazuki:20191015101902p:plain

念のため自分が Publisher のアプリを全部消してクリーンにしてから、上のパッケージをインストールすると無事動きました。

f:id:okazuki:20191015102104p:plain

めでたしめでたし。


MSIX でパッケージングしたアプリから見えるファイルシステムとレジストリの確認方法

$
0
0

知らなかったけど、こんなコマンドがあったんだ。

docs.microsoft.com

Package.appxmanifestの Packaging タブで Package family name:を控えてこんな感じのコマンドでパッケージ化されたアプリから見えるレジストリキーが見えた

Invoke-CommandInDesktopPackage -PackageFamilyName "a3c6c55d-67da-49c4-ba72-d68f97906c69_bvcq4yqpbrjyj" -Command "regedit.exe" -PreventBreakaway

これで、パッケージ化されたアプリから見えるファイルシステムが見えるコマンドプロンプトが立ち上がった。

Invoke-CommandInDesktopPackage -PackageFamilyName "a3c6c55d-67da-49c4-ba72-d68f97906c69_bvcq4yqpbrjyj" -Command "cmd.exe" -PreventBreakaway

パッケージ化されたアプリから AppData の下に MyTestApp フォルダーを作って mytestdata.txt に適当にデータを書き込んでみるようなコードを書いてみました。

privatevoid Button_Click(object sender, RoutedEventArgs e)
{
    var path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
    var appFolder = Path.Combine(path, "MyTestApp");
    Directory.CreateDirectory(appFolder);
    using (var sw = new StreamWriter(Path.Combine(appFolder, "mytestdata.txt")))
    {
        sw.WriteLine("Hello world");
    }
}

先ほどのコマンドプロンプトから AppData フォルダーに移動してメモ帳起動してみたらばっちりですね。

f:id:okazuki:20191025102050p:plain

まとめ

困ったときに便利そう。

.NET Core 3.0 + Windows 10 で WPF 開発 というタイトルで .NET Conf 2019 で登壇してきました

$
0
0

登壇資料アップロードしました。

www.slideshare.net

関連記事

基本的に、過去のブログで結構書いてた内容のまとめになります。

同じやるなた新しい技術使えたほうが楽しいので、是非デスクトップアプリ開発する機会がある人は虎視眈々と導入機会を伺ってください!

XAML Islands を使ったときのメリットの1つ「テーマ対応」

$
0
0

ツイートしようとしたら、録画したGifアニメを受け付けてくれなかったのでBlogにポスト。

f:id:okazuki:20191028154304g:plain

画面全体を XAML Islands で覆ってる場合は、上記のようにテーマ変更にも対応してくれます。いいね。

MFC に XAML Islands で UWP のコントロールを追加してみよう

$
0
0

MFC に UWP のコントロール置けるってさ。

やってみよう

MFC アプリを新規作成します。今回は XAMLIslandsMFCApp という名前で SDI アプリケーションでいってみたいと思います。残りはデフォの設定で作成!実行するとこんな感じです。 懐かしい。(Borland C++ Builder を昔はメインに使ってたけど、少しだけ MFC もかじってた)

f:id:okazuki:20191101152935p:plain

パッケージングしよう

とりあえず msix にパッケージングします。しなくてもできるのですが、なんか自分のところだとうまくいかなかったので今回は妥協という感じで。 Windows アプリケーション パッケージ プロジェクトをさくっと追加してアプリケーションノードに MFC アプリのプロジェクトを追加します。

現時点では Windows 10 1903 でしかサポートされてないので、プロジェクトを作るときは Target version と Minimum version できちんと 1903 を選びましょう。

f:id:okazuki:20191101153301p:plain

ソリューションエクスプローラーは以下のような感じになります。

f:id:okazuki:20191101153403p:plain

次に MFC アプリに C++/WinRT の NuGet パッケージを追加します。

  • Microsoft.Windows.CppWinRT

そして XAML Islands 用のパッケージも追加します。

  • Microsoft.Toolkit.Win32.UI.SDK (現時点では rc1 なのでプレビューパッケージにチェックを入れてインストールしてください)

pch.hに XAML Islands 関連や UWP 関連のヘッダーファイルの includeを追加します。pragmaundefは、今のところこれを追加しないとコンパイルエラーになるバグの回避用です。将来的にはいらなくなるでしょう。ここか、各ファイルに使う機能の入ったヘッダーファイルの include を追加します。

// pch.h: This is a precompiled header file.// Files listed below are compiled only once, improving build performance for future builds.// This also affects IntelliSense performance, including code completion and many code browsing features.// However, files listed here are ALL re-compiled if any one of them is updated between builds.// Do not add files here that you will be updating frequently as this negates the performance advantage.#ifndef PCH_H#define PCH_H// add headers that you want to pre-compile here#include "framework.h"#pragma push_macro("TRY")#undef GetCurrentTime#undef TRY#include <winrt/base.h>#include <winrt/Windows.Foundation.h>#include <winrt/Windows.Foundation.Collections.h>#include <winrt/Windows.UI.Xaml.h>#include <winrt/Windows.UI.Xaml.Controls.h>#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>#include <winrt/Windows.UI.Xaml.Hosting.h>#include <winrt/Windows.UI.Popups.h>#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>#pragma pop_macro("TRY")#pragma pop_macro("GetCurrentTime") #endif//PCH_H

XAMLIslandsMFCApp.hを開いて CXAMLIslandsMFCAppApp (プロジェクトの名前付けミスった…!) クラスの private なメンバー変数に winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager型の変数を追加します。

class CXAMLIslandsMFCAppApp : public CWinAppEx
{
public:
    CXAMLIslandsMFCAppApp() noexcept;

private:
    winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager _windowsXamlManager{ nullptr }; // add// Overridespublic:
    virtual BOOL InitInstance();
    virtualint ExitInstance();

// Implementation
    UINT  m_nAppLook;
    BOOL  m_bHiColorIcons;

    virtualvoid PreLoadState();
    virtualvoid LoadCustomState();
    virtualvoid SaveCustomState();

    afx_msg void OnAppAbout();
    DECLARE_MESSAGE_MAP()
};

そして CXAMLIslandsMFCAppAppクラスの InitInstanceメソッドの CWinAppEx::InitInstanceメソッドの呼び出しの前あたりに XAML Islands の初期化処理を書きます。

BOOL CXAMLIslandsMFCAppApp::InitInstance()
{
    // InitCommonControlsEx() is required on Windows XP if an application// manifest specifies use of ComCtl32.dll version 6 or later to enable// visual styles.  Otherwise, any window creation will fail.
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    // Set this to include all the common control classes you want to use// in your application.
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    winrt::init_apartment(winrt::apartment_type::single_threaded); // add
    _windowsXamlManager = winrt::Windows::UI::Xaml::Hosting::WindowsXamlManager::InitializeForCurrentThread(); // add

    CWinAppEx::InitInstance();

        ... 省略 ...
}

これで下準備が出来ました。コントロールを追加していきます。

CXAMLIslandsMFCAppViewクラスに XAML Islands をホストするウィンドウを管理してくれる winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceクラスのメンバー変数を作ります。

private:
    winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource _xamlIsland{ nullptr };

クラスウィザードを使って CXAMLIslandsMFCAppViewクラスに WM_CREATEWM_CLOSEWM_SIZEのメッセージハンドラーを追加します。 OnCreateメソッドで DesktopWindowXamlSourceのインスタンスを作ってコントロールを置いていきます。

int CXAMLIslandsMFCAppView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // create a DesktopWindowXamlSource instance
    _xamlIsland = winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource{};
    auto interop = _xamlIsland.as<IDesktopWindowXamlSourceNative>();
        // attach to current view
    interop->AttachToWindow(GetSafeHwnd());

    // create uwp controls
    winrt::Windows::UI::Xaml::Controls::TextBox textBox;
    winrt::Windows::UI::Xaml::Controls::Button button;
    button.Content(winrt::box_value(winrt::hstring(L"Click!!")));

    winrt::Windows::UI::Xaml::Controls::StackPanel panel;
    panel.Children().Append(textBox);
    panel.Children().Append(button);
    // set the uwp control instance to the DesktopWindowXamlSource instance
    _xamlIsland.Content(panel);

    return0;
}

DesktopWindowXamlSourceがウィンドウで、この上に UWP のコントロールが置けます。そして、このウィンドウに UWP のコントロールを置く感じです。

OnCloseでは後しますをします。

void CXAMLIslandsMFCAppView::OnClose()
{
    _xamlIsland.Close();
    _xamlIsland = nullptr;

    CView::OnClose();
}

OnSizeではViewのサイズに応じて DesktopWindowXamlSourceのサイズを調整します。

void CXAMLIslandsMFCAppView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    // fit to this viewauto interop = _xamlIsland.as<IDesktopWindowXamlSourceNative>();
    HWND islandHwnd = NULL;
    winrt::check_hresult(interop->get_WindowHandle(&islandHwnd));
    RECT viewRect{};
    GetWindowRect(&viewRect);
    ::SetWindowPos(islandHwnd, NULL, 0, 0,  viewRect.right - viewRect.left, viewRect.bottom - viewRect.top, SWP_SHOWWINDOW);
    auto p = _xamlIsland.Content().as<winrt::Windows::UI::Xaml::Controls::StackPanel>();
    p.UpdateLayout();
}

実行してみましょう。UWP のコントロールが表示されます。

f:id:okazuki:20191101162901p:plain

イベントハンドラーも追加してみましょう。

イベント購読解除用の winrt::event_revokerとイベントハンドラー用のメンバー関数を CXAMLIslandsMFCAppViewに追加します。

private:
    winrt::Windows::UI::Xaml::Controls::Button::Click_revoker _clickRevoker;
    winrt::Windows::Foundation::IAsyncAction OnButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);

OnCreateでイベントの登録処理を追加して

// ボタンを作った直後くらいにこの処理を入れる
_clickRevoker = button.Click(winrt::auto_revoke, { this, &CXAMLIslandsMFCAppView::OnButtonClick });

OnCloseには購読解除処理も入れておきます。

void CXAMLIslandsMFCAppView::OnClose()
{
    _clickRevoker.revoke();
    _xamlIsland.Close();
    _xamlIsland = nullptr;

    CView::OnClose();
}

そして、イベントハンドラーに適当に処理を追加します。今回は入力した内容をそのままメッセージボックスに出します。

winrt::Windows::Foundation::IAsyncAction CXAMLIslandsMFCAppView::OnButtonClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e)
{
    auto input = _xamlIsland
        .Content()
        .as<winrt::Windows::UI::Xaml::Controls::StackPanel>()
        .Children()
        .GetAt(0)
        .as<winrt::Windows::UI::Xaml::Controls::TextBox>()
        .Text();
    winrt::Windows::UI::Popups::MessageDialog dialog{ input };
    co_await dialog.ShowAsync();
}

実行すると、こんな感じになります。

f:id:okazuki:20191101165618p:plain

まとめ

ここまでで見た目は出来た雰囲気です。 でもフォーカス制御やメッセージをディスパッチしてあげるなどの処理がまだ必要です…。つらたん…。詳細はここにあるのですがまだよくわからんのですよ。

github.com

Visual Studio Code Online Public preview 試してみた!

$
0
0

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 Code 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 Code Online 拡張機能を入れて VS ONLINE に繋いてみました。VS Code Online の先にリモート開発機能でつないでる感じですね。

f:id:okazuki:20191105013700p:plain

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

f:id:okazuki:20191105014026p:plain

感想

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

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

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

Viewing all 1388 articles
Browse latest View live


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