rksoftware

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

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

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

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

WPF で試した記事はこちら

当時と今では状況も違うはずですし今度は Windows フォームアプリケーションで試してみます。

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

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

以前は、プレリリースを含めるのチェックが必要だった記憶がありますが、今は不要のようです。
ダウンロード件数がとても気になりますが今は気にしないでおきましょう。

■ デザイナで XamlHost コントロールを配置

NuGet パッケージをインストールすると、XamlHost コントロールが使えるようになるので、デザイナで配置します。
f:id:rksoftware:20190103214146j:plain

■ 参照の設定

以前は 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 と設定します。
f:id:rksoftware:20190103214206j:plain

InitialTypeName で指定したコントロールのプロパティをデザイナから設定する手段はありません。実際に生成されたイベントの中で設定をします。
XamlHost の ChildChanged イベントにハンドラを設定します。
f:id:rksoftware:20190103214239j:plain

デザインのコード

デザインのコードは次のように設定しました。

// 
// windowsXamlHost1
// 
this.windowsXamlHost1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
this.windowsXamlHost1.InitialTypeName = "Windows.UI.Xaml.Controls.ProgressRing";
this.windowsXamlHost1.Location = new System.Drawing.Point(40, 10);
this.windowsXamlHost1.Name = "windowsXamlHost1";
this.windowsXamlHost1.Size = new System.Drawing.Size(300, 200);
this.windowsXamlHost1.TabIndex = 0;
this.windowsXamlHost1.Text = "windowsXamlHost1";
this.windowsXamlHost1.ChildChanged += new System.EventHandler(this.windowsXamlHost1_ChildChanged);

イベントハンドラ

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

private void windowsXamlHost1_ChildChanged(object sender, EventArgs e)
{
    var host = (Microsoft.Toolkit.Forms.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:20190103214309j:plain

■ InkCanvas

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

デザインのコード

namespace WindowsFormsApp1
{
    partial class Form1
    {
        /// <summary>
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナーで生成されたコード

        /// <summary>
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.windowsXamlHost1 = new Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost();
            this.windowsXamlHost2 = new Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost();
            this.SuspendLayout();
            // 
            // windowsXamlHost1
            // 
            this.windowsXamlHost1.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
            this.windowsXamlHost1.InitialTypeName = "Windows.UI.Xaml.Controls.ProgressRing";
            this.windowsXamlHost1.Location = new System.Drawing.Point(40, 10);
            this.windowsXamlHost1.Name = "windowsXamlHost1";
            this.windowsXamlHost1.Size = new System.Drawing.Size(300, 200);
            this.windowsXamlHost1.TabIndex = 0;
            this.windowsXamlHost1.Text = "windowsXamlHost1";
            this.windowsXamlHost1.ChildChanged += new System.EventHandler(this.windowsXamlHost1_ChildChanged);
            // 
            // windowsXamlHost2
            // 
            this.windowsXamlHost2.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly;
            this.windowsXamlHost2.InitialTypeName = "Windows.UI.Xaml.Controls.InkCanvas";
            this.windowsXamlHost2.Location = new System.Drawing.Point(440, 10);
            this.windowsXamlHost2.Name = "windowsXamlHost2";
            this.windowsXamlHost2.Size = new System.Drawing.Size(300, 200);
            this.windowsXamlHost2.TabIndex = 1;
            this.windowsXamlHost2.Text = "windowsXamlHost2";
            this.windowsXamlHost2.ChildChanged += new System.EventHandler(this.windowsXamlHost2_ChildChanged);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(784, 221);
            this.Controls.Add(this.windowsXamlHost2);
            this.Controls.Add(this.windowsXamlHost1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);

        }

        #endregion

        private Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost windowsXamlHost1;
        private Microsoft.Toolkit.Forms.UI.XamlHost.WindowsXamlHost windowsXamlHost2;
    }
}

イベントハンドラ

using System;
using System.Windows.Forms;

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

        private void windowsXamlHost1_ChildChanged(object sender, EventArgs e)
        {
            var host = (Microsoft.Toolkit.Forms.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 windowsXamlHost2_ChildChanged(object sender, EventArgs e)
        {
            var host = (Microsoft.Toolkit.Forms.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; という設定を行っています。

■ 実行

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

■ おまけ .NET Core 3.0

以前に .NET Core 3.0 で Windows フォームアプリケーションを作る記事を書きました。
デスクトップアプリも .NET Core で作る時代が近づいています。XAML 諸島が .NET Core でも使えるか試してみました。
結果は

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

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