rksoftware

Visual Studio とか C# とかが好きです

WPF や Windows フォームアプリケーションで最新機能に対応する XAML Islands (目次)

Windows 10 October Update (Version 1809) で XAML Islands という機能が使えるようになりました。Windows 10 の UI コントロール (UWP のコントロール) が WPF や Windows Forms アプリケーションで使える夢の機能です。
詳細は公式ドキュメント

XAML Islands は開発者 Preview ではありますすが、ビルドして実行できます。この記事は、いくつか実際に試してみた記事の目次です。

■ WPF 関連

□ WPF アプリケーションで最新機能に対応する XAML Islands

□ もう一つの XAML 諸島のコントロールの設定方法 (WPF編)

□ WPF でUWP の ProgressRing を表示する

□ XAML Islands の Windows Community Toolkit でラップされたコントロール (WPF編)

■ WinForms 関連

□ Windows フォームアプリケーションで最新機能に対応する XAML Islands

□ もう一つの XAML 諸島のコントロールの設定方法

□ XAML Islands の Windows Community Toolkit でラップされたコントロール (WinForms編)

■ 共通・トラブルシュート

□ XAML 諸島を使った際にエラーが発生する場合

□ XAML Islands の使用時に表示が思い通りにならなかった件について

□ XAML Islands で表示スケールに対応する

□ XAML Islands で InitializeForCurrentThread メソッドが必要なパターン

□ XAML Islands でコントロールを一通り表示してみる

□ XAML Islands の MapControl 使用時に InvalidComObjectException 例外 (WinForms)

□ XAML Islands の MediaPlayerElement 使用時に動画の再生が始まらない (WinForms)

.NET Core 3.0 でデスクトップアプリを作る (目次)

.NET Core はバージョン 3.0 の新機能として Windows デスクトップ アプリケーションをサポートします。まだ Preview ではありますが、ビルドして実行できます。

この記事は、いくつか実際に試してみた記事の目次です。

■ 2018年10月頃

この頃に .NET Core 3.0 alpha 版 + Visual Studio 2017 15.9 Preview 版で実際にビルドして実行できるようになりました。.NET Core 3.0 でのデスクトップアプリ開発の最初の一歩です。

.NET Core 3.0 でデスクトップアプリを作る(VS プレビュー版を使わない)

■ 2018年12月頃

この頃に .NET Core 3.0 preview 版 + Visual Studio 2019 Preview 版でより実装が進みストレスなくコードを書いてビルドして実行できるようになりました。alpha 版から変わったポイントも多くあります。

□ .NET Core 3.0 プロジェクトの発行でエラーになる

□ .NET Core 3.0 デスクトップアプリプロジェクトが Visual Studio 2019 で開けない

□ .NET Core 3.0 デスクトップアプリプロジェクトがビルドできない

□ デザイナがなくても問題なし .NET Core 3.0 で Windows フォームアプリケーションを作る

□ Windows フォーム手書き時の注意 AutoScaleDimensions 設定

■ .NET Core のコマンド関係

.NET Framework では 多くのことが Visual Studio の GUI で操作してきましたが .NET Core でコマンドラインを使う機会が増えました。.NET Core ではコマンドラインでの操作にもなれて行く必要があります。

□ .NET Core のコマンドの一例

□ dotnet core のインストールされていないない環境でも動作する dotnet core アプリケーションを作る

■ その他の検証

□ .NET Core デスクトップアプリケーションから DioDocs を使って帳票を PDF 出力する

□ .NET Core デスクトップアプリケーションで PDF 帳票を画面表示する

□ .NET Core デスクトップアプリケーションで DioDocs で作った PDF 帳票を印刷する

XAML Islands の使用時に表示が思い通りにならなかった件について

先日、WPF アプリケーションでの XAML Islands を試す記事を書きました。

その中で、レイアウトが思った通りに表示されないという場面がありました。

期待した表示

f:id:rksoftware:20190109225857j:plain

実際の表示

f:id:rksoftware:20190109225926j:plain

■ 再現方法

Windows の表示スケールを変更します。

  • 期待した表示 はスケール100%で実行した表示です。
  • 実際の表示 はスケール175%で実行した表示です。

私が普段使ってる環境が175%だったので期待した表示になりませんでした。

■ 未サポート

まだ、表示スケールの変更には未対応のようです。

もう一つの XAML 諸島のコントロールの設定方法 (WPF編)

先日、WPF アプリケーションでの XAML Islands を試す記事を書きました。

しかしこの方法だと、使う UWP のエレメント名を文字列で指定せねばならず、プロパティも ChildChanged イベントでの設定になます。また階層化された複数のエレメントを配置するなどもできません。

■ コードでコントロールを生成する

最近このパターンばかりですが、コードで全てを設定すれば解決します。
次のように使いたいエレメントをインスタンス化して、WindowsXamlHost の Child プロパティに設定しても使えるようです。

using System;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            {
                var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
                {
                    IsActive = true,
                    Width = 300,
                    Height = 200,
                };
                windowsXamlHost1.Child = progressRing;
            }

            {
                var inkCanvas = new Windows.UI.Xaml.Controls.InkCanvas
                {
                    Width = 300,
                    Height = 200,
                };
                inkCanvas.InkPresenter.InputDeviceTypes =
                    Windows.UI.Core.CoreInputDeviceTypes.Mouse
                    | Windows.UI.Core.CoreInputDeviceTypes.Pen;
                windowsXamlHost2.Child = inkCanvas;
            }
        }
    }
}

f:id:rksoftware:20190106151255j:plain

■ 階層化

エレメントを階層する場合もこの様に。

using System;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var grid = new Windows.UI.Xaml.Controls.Grid();

            {
                var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
                {
                    IsActive = true,
                    Width = 300,
                    Height = 200,
                };

                grid.Children.Add(progressRing);
            }

            {
                var inkCanvas = new Windows.UI.Xaml.Controls.InkCanvas
                {
                    Width = 300,
                    Height = 200,
                };
                inkCanvas.InkPresenter.InputDeviceTypes =
                    Windows.UI.Core.CoreInputDeviceTypes.Mouse
                    | Windows.UI.Core.CoreInputDeviceTypes.Pen;

                grid.Children.Add(inkCanvas);
            }

            windowsXamlHost1.Child = grid;
        }
    }
}

この例では、ProgressRing と InkCanvas を Grid の子要素として重ねて表示されるようにしています。 f:id:rksoftware:20190106151312j:plain

注意

私の環境では行わなくても動作しましたが、これらの方法はドキュメントによると次の一文が最初に必要だと書いてあるような気がします。

global::Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread();

この WindowsXamlManager クラスの InitializeForCurrentThread メソッドですが、Windows.UI.Xaml.Hosting.HostingContract (C:\Program Files (x86)\Windows Kits\10\References\<バージョン>\Windows.UI.Xaml.Hosting.HostingContract\<バージョン>\Windows.UI.Xaml.Hosting.HostingContract.winmd) を参照に追加すると使用できました。

追記

InitializeForCurrentThread が必要になるパターンを確認してみました。

WPF アプリケーションで最新機能に対応する XAML Islands

Windows のバージョン 1809 で XAML Islands (MS のドキュメントの翻訳では XAML 諸島) という機能が使えるようになりました。
Windows 10 の UI コントロール (UWP のコントロール) が WPF や Windows Forms アプリケーションで使える夢の機能です。

詳細は公式ドキュメントへ

Windows フォームで試した記事はこちら

今度は WPF アプリケーションで試してみます。

■ NuGet パッケージのインストール

まずは、NuGet で Microsoft.Toolkit.Wpf.UI.XamlHost をインストールします。
f:id:rksoftware:20190106145756j:plain

以前は、プレリリースを含めるのチェックが必要だった記憶がありますが、今は不要のようです。

■ XAML で XamlHost コントロールを配置

NuGet パッケージをインストールすると、XamlHost コントロールが使えるようになるので、XAML で配置します。

xmlns の追加

これが一番の難関でしょう。Window に次の属性を追加します。

xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"

Xamlost エレメントの配置

いつものように XAML を書いて行きます。

<xamlhost:WindowsXamlHost Width="300" Height="200"
                          ChildChanged="WindowsXamlHost_ChildChanged"
                          InitialTypeName="Windows.UI.Xaml.Controls.ProgressRing"/>

■ 参照の設定

以前は 6 つほど参照を追加した記憶がありますが、今回は二つだけで済みました。
※もっと多くのコントロールのプロパティなどを設定する場合、他にも参照がいつ様になります

ファイル 場所
Windows.Foundation.UniversalApiContract.winmd C:\Program Files (x86)\Windows Kits\10\References<sdk version>\Windows.Foundation.UniversalApiContract<version>
Windows.WinMD C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Facade

参考
https://docs.microsoft.com/ja-jp/windows/uwp/porting/desktop-to-uwp-enhance#first-set-up-your-project

■ プロパティを設定

XamlHost コントロールは、UWP のコントロールを入れるられるコンテナのようなものです。実際に中に配置するコントロールを設定します。今回もまずは ProgressRing を表示してみます。
XamlHost のプロパティ InitialTypeNameWindows.UI.Xaml.Controls.ProgressRing と設定しています。

InitialTypeName で指定したコントロールのプロパティを XML から設定するものであないようです。実際に生成されたイベントの中で設定をします。
XamlHost の ChildChanged イベントにハンドラを設定します。

<xamlhost:WindowsXamlHost Width="300" Height="200"
                          ChildChanged="WindowsXamlHost_ChildChanged"
                          InitialTypeName="Windows.UI.Xaml.Controls.ProgressRing"/>

XAML

XAML は次のようにしました。

<Window x:Class="WpfApp1.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:WpfApp1"
        xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400" Background="LightGray">
    <Grid>
        <xamlhost:WindowsXamlHost Width="300" Height="200"
                                  ChildChanged="WindowsXamlHost_ChildChanged"
                                  InitialTypeName="Windows.UI.Xaml.Controls.ProgressRing"/>
    </Grid>
</Window>

イベントハンドラ

イベントハンドラの中で ProgressRing のプロパティを設定します。

private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
{
    var host = (Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost)sender;
    var progressRing = (Windows.UI.Xaml.Controls.ProgressRing)host.Child;
    if (progressRing != null)
    {
        progressRing.IsActive = true;
        progressRing.Width = 300;
        progressRing.Height = 200;
    }
}

■ 実行

実行してみると、UWP の ProgressRing が表示されました。これで時間のかかる処理を行う際も安心ですね。
f:id:rksoftware:20190106145822j:plain

ちょっとレイアウトが思っているのと違ってしまいました、こちらは今後検証して行きたいと思います。

■ InkCanvas

UWP のコントロールが使えると聞いて皆が気になるあのコントロール InkCanvas も試してみます。
XamlHost コントロールをもう一つ配置して各種設定をして行きます。

XAML

<Window x:Class="WpfApp1.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:WpfApp1"
        xmlns:xamlhost="clr-namespace:Microsoft.Toolkit.Wpf.UI.XamlHost;assembly=Microsoft.Toolkit.Wpf.UI.XamlHost"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="800" Background="LightGray">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <xamlhost:WindowsXamlHost Grid.Column="0" Width="300" Height="200"
                                  ChildChanged="WindowsXamlHost_ChildChanged"
                                  InitialTypeName="Windows.UI.Xaml.Controls.ProgressRing"/>

        <xamlhost:WindowsXamlHost Grid.Column="1" Width="300" Height="200"
                                  ChildChanged="WindowsXamlHost_ChildChanged_1"
                                  InitialTypeName="Windows.UI.Xaml.Controls.InkCanvas"/>
    </Grid>
</Window>

イベントハンドラ

using System;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void WindowsXamlHost_ChildChanged(object sender, EventArgs e)
        {
            var host = (Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost)sender;
            var progressRing = (Windows.UI.Xaml.Controls.ProgressRing)host.Child;
            if (progressRing != null)
            {
                progressRing.IsActive = true;
                progressRing.Width = 300;
                progressRing.Height = 200;
            }
        }

        private void WindowsXamlHost_ChildChanged_1(object sender, EventArgs e)
        {
            var host = (Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost)sender;
            var inkCanvas = (Windows.UI.Xaml.Controls.InkCanvas)host.Child;
            if (inkCanvas != null)
            {
                inkCanvas.Width = 300;
                inkCanvas.Height = 200;
                inkCanvas.InkPresenter.InputDeviceTypes =
                    Windows.UI.Core.CoreInputDeviceTypes.Mouse
                    | Windows.UI.Core.CoreInputDeviceTypes.Pen;
            }
        }
    }
}

InlCanvas にマウスで書けるように inkCanvas.InkPresenter.InputDeviceTypes = Windows.UI.Core.CoreInputDeviceTypes.Mouse | Windows.UI.Core.CoreInputDeviceTypes.Pen; という設定を行っています。

■ 実行

実行して文字を書いてみました。これで最新機能を実装する WPF アプリケーションという要件があっても安心ですね。
f:id:rksoftware:20190106145846j:plain

■ おまけ .NET Core 3.0

先日 .NET Core 3.0 での Windows フォームアプリケーションでは XAML Islands がまだ使えないませんでした。

デスクトップアプリも .NET Core で作る時代が近づいています。せっかくなので WPF でも XAML 諸島が .NET Core でも使えるか試してみました。
結果は

エラー NU1202 パッケージ Microsoft.Toolkit.Wpf.UI.XamlHost 5.0.1 は netcoreapp3.0 (.NETCoreApp,Version=v3.0) / win-x64 と互換性がありません。 パッケージ Microsoft.Toolkit.Wpf.UI.XamlHost 5.0.1 がサポートするもの: net462 (.NETFramework,Version=v4.6.2)

まだ使えないようです。残念。

もう一つの XAML 諸島のコントロールの設定方法

先日、Windows フォームアプリケーションでの XAML Islands を試す記事を書きました。

しかしこの方法だと、使う UWP のエレメント名を文字列で指定せねばならずプロパティも ChildChanged イベントでの設定になります。また階層化された複数のエレメントを配置するなどもできません。

■ コードでコントロールを生成する

最近このパターンばかりですが、コードで全てを設定すれば解決します。
次のように使いたいエレメントをインスタンス化して、WindowsXamlHost の Child プロパティに設定しても使えるようです。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            {
                var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
                {
                    IsActive = true,
                    Width = 300,
                    Height = 200,
                };
                windowsXamlHost1.Child = progressRing;
            }

            {
                var inkCanvas = new Windows.UI.Xaml.Controls.InkCanvas
                {
                    Width = 300,
                    Height = 200,
                };
                inkCanvas.InkPresenter.InputDeviceTypes =
                    Windows.UI.Core.CoreInputDeviceTypes.Mouse
                    | Windows.UI.Core.CoreInputDeviceTypes.Pen;
                windowsXamlHost2.Child = inkCanvas;
            }
        }
    }
}

f:id:rksoftware:20190105203503j:plain

■ 階層化

エレメントを階層する場合もこの様に。

using System;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            var grid = new Windows.UI.Xaml.Controls.Grid();

            {
                var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
                {
                    IsActive = true,
                    Width = 300,
                    Height = 200,
                };

                grid.Children.Add(progressRing);
            }

            {
                var inkCanvas = new Windows.UI.Xaml.Controls.InkCanvas
                {
                    Width = 300,
                    Height = 200,
                };
                inkCanvas.InkPresenter.InputDeviceTypes =
                    Windows.UI.Core.CoreInputDeviceTypes.Mouse
                    | Windows.UI.Core.CoreInputDeviceTypes.Pen;

                grid.Children.Add(inkCanvas);
            }

            windowsXamlHost1.Child = grid;
        }
    }
}

この例では、ProgressRing と InkCanvas を Grid の子要素として重ねて表示されるようにしています。 f:id:rksoftware:20190105203529j:plain

注意

私の環境では行わなくても動作しましたが、これらの方法はドキュメントによると次の一文が最初に必要だと書いてあるような気がします。

global::Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread();

この WindowsXamlManager クラスの InitializeForCurrentThread メソッドですが、Windows.UI.Xaml.Hosting.HostingContract (C:\Program Files (x86)\Windows Kits\10\References\<バージョン>\Windows.UI.Xaml.Hosting.HostingContract\<バージョン>\Windows.UI.Xaml.Hosting.HostingContract.winmd) を参照に追加すると使用できました。

XAML 諸島を使った際にエラーが発生する場合

XAML Islands を試している際に次の例外が出ることがありました。

Microsoft.Windows.Interop.UWPTypeFactory: Could not create type: XXXXXXXXXX

原因は、InitialTypeName の設定ミスです。
このプロパティには 使うエレメントの名前を正確に (例えば "Windows.UI.Xaml.Controls.InkCanvas" のように) 書かねばなりません。しかしこのプロパティの型、文字列なので書き間違えていてもコンパイル時などにチェックしてくれません。

エレメントの名前を書き間違えないように注意して、場合によっては複数人でのダブルチェック、トリプルチェックなどを駆使して、正確に記述するよう気を付けましょう。