rksoftware

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

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

XAML Islands についての公式ドキュメントの ラップされたコントロール というセクションで 6 つのコントロールが挙げられています。

ラップされたコントロール

  • WebView
  • WebViewCompatible
  • InkCanvas
  • InkToolbar
  • MediaPlayerElement
  • MapControl

これらのコントロールはこれまで使ってきた WindowsXamlHost コントロールを使わずに使用することができます。

■ コード

コードを見るのが手っ取り早いと思うのでコード例です。

<Window x:Class="WpfApp1.Window2"
        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:xamlhostwebview="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView"
        xmlns:xamlhostcontrols="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="Window2" Height="600" Width="800">
    <StackPanel Background="AliceBlue">
        <xamlhostwebview:WebView Height="100" Width="600" Margin="1" Source="https://rksoftware.hatenablog.com/"/>
        <xamlhostcontrols:InkCanvas Height="100" Width="600" Margin="1" x:Name="inkCanvas"/>
        <xamlhostcontrols:InkToolbar Height="100" Width="600" Margin="1"/>
        <xamlhostcontrols:MapControl Height="100" Width="600" Margin="1"/>
        <xamlhostcontrols:MediaPlayerElement Height="100" Width="600" Margin="1"
                                             Source="https://mediaplatstorage1.blob.core.windows.net/windows-universal-samples-media/elephantsdream-clip-h264_sd-aac_eng-aac_spa-aac_eng_commentary-srt_eng-srt_por-srt_swe.mkv"
                                             AutoPlay="True" 
                                             />
    </StackPanel>
</Window>
using System.Windows;

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

            inkCanvas.InkPresenter.InputDeviceTypes =
                Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Mouse
                | Microsoft.Toolkit.Win32.UI.Controls.Interop.WinRT.CoreInputDeviceTypes.Pen;
        }
    }
}

実行結果

f:id:rksoftware:20190121014701j:plain

※MediaPlayerElement の Source は次のドキュメントの設定を使わせてもらいました。
https://docs.microsoft.com/en-us/windows/communitytoolkit/controls/wpf-winforms/mediaplayerelement

WindowsXamlHost コントロールを介さず直接 XAML にコントロールを書けますし、プロパティも XAML で設定できます。ラップされたコントロール で書かれているコントロールはこの形式で使えるようになっているコントロールということで、最終的には一通りのコントロールをこの形で使えるようにする予定なのでしょう。

■ NuGet

前述のコントロール達を使うには次の NuGet パッケージをインストールします。

  • WebView
  • WebViewCompatible
Microsoft.Toolkit.Wpf.UI.Controls.WebView
  • InkCanvas
  • InkToolbar
  • MediaPlayerElement
  • MapControl
Microsoft.Toolkit.Wpf.UI.Controls

オブジェクトブラウザで見るとこんな感じです。
f:id:rksoftware:20190121014731j:plain

■ DPI 関係の問題

ただし、High DPI 環境で未サポートの事項があるので WPF で使う場合には、 WindowsXamlHost コントロールを使って回避するのも今の段階では一つの道かもしれません。

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

XAML Islands で全ての UWP コントロールは使えなさそうな雰囲気だったので、Windows.UI.Xaml.Controls 名前空間のそれらしいクラスを画面に配置してみました。
UWP のコントロールは 100 以上あるらしく全ては把握押していないので画面に配置してエラーになるかならないかだけが判断基準です。
また、他にも UIElement はありそうな気もしますが一歩目の検証として試してみました。

■ 結果

全てではありませんでしたが大多数がエラーなく配置できました。
f:id:rksoftware:20190120193905j:plain

■ エラーにならなかったコントロール

  • AppBar
  • AppBarButton
  • AppBarElementContainer
  • AppBarSeparator
  • AppBarToggleButton
  • AutoSuggestBox
  • BitmapIcon
  • Border
  • Button
  • CalendarDatePicker
  • CalendarView
  • Canvas
  • CaptureElement
  • CheckBox
  • ComboBox
  • ComboBoxItem
  • CommandBar
  • CommandBarOverflowPresenter
  • ContentControl
  • ContentDialog
  • ContentPresenter
  • DatePicker
  • DropDownButton
  • FlipView
  • FlipViewItem
  • FlyoutPresenter
  • FontIcon
  • Frame
  • Grid
  • GridView
  • GridViewHeaderItem
  • GridViewItem
  • GroupItem
  • Hub
  • HubSection
  • HyperlinkButton
  • IconSourceElement
  • Image
  • InkCanvas
  • InkToolbar
  • InkToolbarBallpointPenButton
  • InkToolbarCustomPenButton
  • InkToolbarCustomToggleButton
  • InkToolbarCustomToolButton
  • InkToolbarEraserButton
  • InkToolbarFlyoutItem
  • InkToolbarHighlighterButton
  • InkToolbarPencilButton
  • InkToolbarPenConfigurationControl
  • InkToolbarStencilButton
  • ItemsControl
  • ItemsPresenter
  • ItemsStackPanel
  • ItemsWrapGrid
  • ListBox
  • ListBoxItem
  • ListView
  • ListViewHeaderItem
  • ListViewItem
  • Maps.MapControl
  • MediaElement
  • MediaPlayerElement
  • MediaPlayerPresenter
  • MediaTransportControls
  • MenuBar
  • MenuBarItem
  • MenuFlyoutItem
  • MenuFlyoutPresenter
  • MenuFlyoutSeparator
  • MenuFlyoutSubItem
  • NavigationView
  • NavigationViewItem
  • NavigationViewItemSeparator
  • NavigationViewList
  • Page
  • ParallaxView
  • PasswordBox
  • PathIcon
  • Pivot
  • PivotItem
  • ProgressBar
  • ProgressRing
  • RadioButton
  • RatingControl
  • RefreshContainer
  • RelativePanel
  • RichEditBox
  • RichTextBlock
  • ScrollContentPresenter
  • ScrollViewer
  • SemanticZoom
  • SettingsFlyout
  • Slider
  • SplitButton
  • SplitView
  • StackPanel
  • SwapChainPanel
  • SymbolIcon
  • TextBlock
  • TextBox
  • TimePicker
  • ToggleMenuFlyoutItem
  • ToggleSplitButton
  • ToggleSwitch
  • ToolTip
  • TreeView
  • TreeViewItem
  • TreeViewList
  • UserControl
  • VariableSizedWrapGrid
  • Viewbox
  • VirtualizingStackPanel
  • WebView

■ 検証コード

<Window x:Class="WpfApp1.Window1"
        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"
        mc:Ignorable="d"
        Title="Window1" Height="450" Width="800">
    <Grid x:Name="grid">
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Windows;

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

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

                var list = new List<Windows.UI.Xaml.UIElement>();
                {
                    list.Add(new Windows.UI.Xaml.Controls.AppBar());
                    list.Add(new Windows.UI.Xaml.Controls.AppBarButton());
                    list.Add(new Windows.UI.Xaml.Controls.AppBarElementContainer());
                    list.Add(new Windows.UI.Xaml.Controls.AppBarSeparator());
                    list.Add(new Windows.UI.Xaml.Controls.AppBarToggleButton());
                    list.Add(new Windows.UI.Xaml.Controls.AutoSuggestBox());
                    list.Add(new Windows.UI.Xaml.Controls.BitmapIcon());
                    list.Add(new Windows.UI.Xaml.Controls.Border());
                    list.Add(new Windows.UI.Xaml.Controls.Button());
                    list.Add(new Windows.UI.Xaml.Controls.CalendarDatePicker());
                    list.Add(new Windows.UI.Xaml.Controls.CalendarView());
                    //list.Add(new Windows.UI.Xaml.Controls.CalendarViewDayItem());
                    list.Add(new Windows.UI.Xaml.Controls.Canvas());
                    list.Add(new Windows.UI.Xaml.Controls.CaptureElement());
                    list.Add(new Windows.UI.Xaml.Controls.CheckBox());
                    //list.Add(new Windows.UI.Xaml.Controls.ColorPicker());
                    list.Add(new Windows.UI.Xaml.Controls.ComboBox());
                    list.Add(new Windows.UI.Xaml.Controls.ComboBoxItem());
                    list.Add(new Windows.UI.Xaml.Controls.CommandBar());
                    list.Add(new Windows.UI.Xaml.Controls.CommandBarOverflowPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.ContentControl());
                    list.Add(new Windows.UI.Xaml.Controls.ContentDialog());
                    list.Add(new Windows.UI.Xaml.Controls.ContentPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.DatePicker());
                    list.Add(new Windows.UI.Xaml.Controls.DropDownButton());
                    list.Add(new Windows.UI.Xaml.Controls.FlipView());
                    list.Add(new Windows.UI.Xaml.Controls.FlipViewItem());
                    list.Add(new Windows.UI.Xaml.Controls.FlyoutPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.FontIcon());
                    list.Add(new Windows.UI.Xaml.Controls.Frame());
                    list.Add(new Windows.UI.Xaml.Controls.Grid());
                    list.Add(new Windows.UI.Xaml.Controls.GridView());
                    list.Add(new Windows.UI.Xaml.Controls.GridViewHeaderItem());
                    list.Add(new Windows.UI.Xaml.Controls.GridViewItem());
                    list.Add(new Windows.UI.Xaml.Controls.GroupItem());
                    //list.Add(new Windows.UI.Xaml.Controls.HandwritingView());
                    list.Add(new Windows.UI.Xaml.Controls.Hub());
                    list.Add(new Windows.UI.Xaml.Controls.HubSection());
                    list.Add(new Windows.UI.Xaml.Controls.HyperlinkButton());
                    list.Add(new Windows.UI.Xaml.Controls.IconSourceElement());
                    list.Add(new Windows.UI.Xaml.Controls.Image());
                    list.Add(new Windows.UI.Xaml.Controls.InkCanvas());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbar());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarBallpointPenButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarCustomPenButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarCustomToggleButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarCustomToolButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarEraserButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarFlyoutItem());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarHighlighterButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarPencilButton());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarPenConfigurationControl());
                    list.Add(new Windows.UI.Xaml.Controls.InkToolbarStencilButton());
                    list.Add(new Windows.UI.Xaml.Controls.ItemsControl());
                    list.Add(new Windows.UI.Xaml.Controls.ItemsPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.ItemsStackPanel());
                    list.Add(new Windows.UI.Xaml.Controls.ItemsWrapGrid());
                    list.Add(new Windows.UI.Xaml.Controls.ListBox());
                    list.Add(new Windows.UI.Xaml.Controls.ListBoxItem());
                    list.Add(new Windows.UI.Xaml.Controls.ListView());
                    list.Add(new Windows.UI.Xaml.Controls.ListViewHeaderItem());
                    list.Add(new Windows.UI.Xaml.Controls.ListViewItem());
                    list.Add(new Windows.UI.Xaml.Controls.Maps.MapControl());
                    list.Add(new Windows.UI.Xaml.Controls.MediaElement());
                    list.Add(new Windows.UI.Xaml.Controls.MediaPlayerElement());
                    list.Add(new Windows.UI.Xaml.Controls.MediaPlayerPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.MediaTransportControls());
                    list.Add(new Windows.UI.Xaml.Controls.MenuBar());
                    list.Add(new Windows.UI.Xaml.Controls.MenuBarItem());
                    list.Add(new Windows.UI.Xaml.Controls.MenuFlyoutItem());
                    list.Add(new Windows.UI.Xaml.Controls.MenuFlyoutPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.MenuFlyoutSeparator());
                    list.Add(new Windows.UI.Xaml.Controls.MenuFlyoutSubItem());
                    list.Add(new Windows.UI.Xaml.Controls.NavigationView());
                    list.Add(new Windows.UI.Xaml.Controls.NavigationViewItem());
                    list.Add(new Windows.UI.Xaml.Controls.NavigationViewItemSeparator());
                    list.Add(new Windows.UI.Xaml.Controls.NavigationViewList());
                    list.Add(new Windows.UI.Xaml.Controls.Page());
                    list.Add(new Windows.UI.Xaml.Controls.ParallaxView());
                    list.Add(new Windows.UI.Xaml.Controls.PasswordBox());
                    list.Add(new Windows.UI.Xaml.Controls.PathIcon());
                    list.Add(new Windows.UI.Xaml.Controls.Pivot());
                    list.Add(new Windows.UI.Xaml.Controls.PivotItem());
                    list.Add(new Windows.UI.Xaml.Controls.ProgressBar());
                    list.Add(new Windows.UI.Xaml.Controls.ProgressRing());
                    list.Add(new Windows.UI.Xaml.Controls.RadioButton());
                    list.Add(new Windows.UI.Xaml.Controls.RatingControl());
                    list.Add(new Windows.UI.Xaml.Controls.RefreshContainer());
                    list.Add(new Windows.UI.Xaml.Controls.RelativePanel());
                    list.Add(new Windows.UI.Xaml.Controls.RichEditBox());
                    list.Add(new Windows.UI.Xaml.Controls.RichTextBlock());
                    list.Add(new Windows.UI.Xaml.Controls.ScrollContentPresenter());
                    list.Add(new Windows.UI.Xaml.Controls.ScrollViewer());
                    list.Add(new Windows.UI.Xaml.Controls.SemanticZoom());
                    list.Add(new Windows.UI.Xaml.Controls.SettingsFlyout());
                    list.Add(new Windows.UI.Xaml.Controls.Slider());
                    list.Add(new Windows.UI.Xaml.Controls.SplitButton());
                    list.Add(new Windows.UI.Xaml.Controls.SplitView());
                    list.Add(new Windows.UI.Xaml.Controls.StackPanel());
                    //list.Add(new Windows.UI.Xaml.Controls.SwapChainBackgroundPanel());
                    list.Add(new Windows.UI.Xaml.Controls.SwapChainPanel());
                    //list.Add(new Windows.UI.Xaml.Controls.SwipeControl());
                    list.Add(new Windows.UI.Xaml.Controls.SymbolIcon());
                    list.Add(new Windows.UI.Xaml.Controls.TextBlock());
                    list.Add(new Windows.UI.Xaml.Controls.TextBox());
                    list.Add(new Windows.UI.Xaml.Controls.TimePicker());
                    list.Add(new Windows.UI.Xaml.Controls.ToggleMenuFlyoutItem());
                    list.Add(new Windows.UI.Xaml.Controls.ToggleSplitButton());
                    list.Add(new Windows.UI.Xaml.Controls.ToggleSwitch());
                    list.Add(new Windows.UI.Xaml.Controls.ToolTip());
                    list.Add(new Windows.UI.Xaml.Controls.TreeView());
                    list.Add(new Windows.UI.Xaml.Controls.TreeViewItem());
                    list.Add(new Windows.UI.Xaml.Controls.TreeViewList());
                    list.Add(new Windows.UI.Xaml.Controls.UserControl());
                    list.Add(new Windows.UI.Xaml.Controls.VariableSizedWrapGrid());
                    list.Add(new Windows.UI.Xaml.Controls.Viewbox());
                    list.Add(new Windows.UI.Xaml.Controls.VirtualizingStackPanel());
                    list.Add(new Windows.UI.Xaml.Controls.WebView());
                    //list.Add(new Windows.UI.Xaml.Controls.WrapGrid());
                };

                Windows.UI.Xaml.Controls.StackPanel stackPanel;
                grid.Children.Add(new Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost
                {
                    Child = new Windows.UI.Xaml.Controls.ScrollViewer
                    {
                        Content = stackPanel = new Windows.UI.Xaml.Controls.StackPanel()
                    }
                });
                foreach (var c in list)
                {
                    stackPanel.Children.Add(c);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

秋葉原 C# もくもく会 #69 勉強会を開催しました

■ C# もくもく会

C# もくもく会 #69 を開催しました。

C# もくもく会 は東京の秋葉原で毎週木曜日に開催している .NET 系の勉強会です。
もくもく自習を基本とし、分からないことを教えあったり情報共有したりしている会です。 定期開催していますので、お時間のある時に遊びに来ていただければと思います。
ちょっと詰まった時、ネット上で聞くのははずかしいなぁ、という課題のできた時などにも思い出していただけると嬉しいです。

f:id:rksoftware:20190120175800j:plain

■ 今週の成果発表

今週は年明けのリハビリ+新しい事をはじめだしたところでといった感じでした。

ちなみにこの勉強会ですが、実は公序良俗に反しなければどのような技術を扱っても大丈夫です。そもそも C# エンジニアが C# だけしか使わないというわけではありませんし。

  • ブログを二記事公開した
  • 作っているモバイルアプリが動くところまできた
  • UWP のコントロールを列挙してみた

■初心者歓迎

このもくもく会には、入門者の方も多くご参加いただいています。 突然 C# やらなければならなくなって途方に暮れている方、何となく C# をやってきたけど改めて見直してみたい方なども大歓迎です。
入門セミナー代わりでのご参加も歓迎です。プログラミング入門者の方も是非遊びに来てください。

特に C# で課題をお持ちでなくても是非遊びに来てください。

■ 目指す勉強会スタイル

世界一敷居の低い勉強会を目指しています。

何か聞きたいことがある場合は、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

何か共有したい情報を見つけた場合も、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

そんなスタイルでやっています。

■ 次回予定

次回は 2019/01/24 に開催予定です。

C# に関心のある方、是非遊びに来てください。

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

以前に XAML Islands で UWP のコントロールを生成する場合、その前に

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

の一文が必要らしいけれど、実際書かなくても大丈夫だったと書きました。
今回この一文を書く必要がある場合と書かなくて良い場合の話です。

■ 結論

  • WindowsXamlHost コントロールを生成する前に UWP コントロールを生成する場合に必要。
  • 既に WindowsXamlHost コントロールを生成しているスレッドでは不要。

です。
言葉だけでは分かりづらいと思いますので、コードで見ていきましょう。

■ エラーとなる例

次のコードは実行時にエラーになります。
WindowsXamlHost を生成する前に UWP のコントロール (ProgressRing) を生成しようとするためです。

using System.Windows;

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

            // UWP のコントロールを生成
            var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
            {
                Height = 300,
                Width = 200,
                IsActive = true,
            };

            // WindowsXamlHost を生成
            var host = new Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost
            {
                Height = 300,
                Width = 200,
            };

            // 画面に配置
            host.Child = progressRing;
            this.AddChild(host);
        }
    }
}

■ InitializeForCurrentThread メソッドでエラーを解消

次のように UWP のコントロールを生成する前に InitializeForCurrentThread メソッドを呼ぶとエラーにならず動作します。

using System.Windows;

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

            // おまじない
            global::Windows.UI.Xaml.Hosting.WindowsXamlManager.InitializeForCurrentThread();

            // UWP のコントロールを生成
            var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
            {
                Height = 300,
                Width = 200,
                IsActive = true,
            };

            // WindowsXamlHost を生成
            var host = new Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost
            {
                Height = 300,
                Width = 200,
            };

            // 画面に配置
            host.Child = progressRing;
            this.AddChild(host);
        }
    }
}

■ コントロール生成順を変更してエラーを解消

次の例もエラーにならず実行できます。
エラーの条件は、WindowsXamlHost を生成する前に UWP のコントロールを生成する、言い換えると「先に WindowsXamlHost を生成していれば OK」です。前述のエラーとなるコードの中のコントロールの生成順を入れ替え最初に WindowsXamlHost を生成するようにすればエラーとならずに動作します。

using System.Windows;

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

            // WindowsXamlHost を生成
            var host = new Microsoft.Toolkit.Wpf.UI.XamlHost.WindowsXamlHost
            {
                Height = 300,
                Width = 200,
            };

            // UWP のコントロールを生成
            var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
            {
                Height = 300,
                Width = 200,
                IsActive = true,
            };

            // 画面に配置
            host.Child = progressRing;
            this.AddChild(host);
        }
    }
}

城東.NET #28 勉強会を開催しました。

■ 城東.NET

城東.NET #28 を開催しました。

城東.NET は東京の最近は秋葉原で毎月第3水曜日に開催している .NET 系の勉強会です。
発表を中心として、発表でなくとも最近やった事や新しい情報などを参加者で共有している会です。

f:id:rksoftware:20190117193404j:plain

今月は、5名の方にご参加いただきました。

発表は

  • XAML Islands
  • VS Live Share

私は XAML Islands というタイトルで話をしました。

今月も VS Live Share でプログラミングをして遊んでいました。
来月も引き続きやって行く予定です。

■ 次回予定

来月は 02月20日(水)に開催の予定です。

.NET に関心のある方、是非遊びに来てください。

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

WPF で XAML Islands を使う際に表示スケールがまだサポートされていないことを以前書きました。

まだサポートされていないと言われているのでそれまで。サポートを待てばよいのですがどうにも辛抱たまらんので頑張ってみました。

■ 表示スケール

ハードウェアの性能向上に伴い、PC のディスプレイのピクセルの細かさも上がってきています。ピクセルの細かい環境でこれまでと同じように、例えば文字を表示しようとすると非常に小さな文字になってしまいます。そこで、Windows には全てを拡大して表示する機能があります。
最近はパソコンを買ってセットアップした直後に 150% ~ 200% で設定されていることも多いので気づかず恩恵を受けている方も多いと思います。

■ 表示スケールに対応していない例

次の例は、表示スケール 175% の Windows 上で XAML Islands を使っています。WindowsXamlHost コントロールとその内部の ProgressRing に同じサイズを設定していますが、ProgressRing が小さく表示されてしまっています。 f:id:rksoftware:20190114192214j:plain

■ 対策 Viewbox 先生

UWP には Viewbox という非常に強力なコントロールがあります。このコントロールは内部のコントロールを自分のサイズに合わせて自動的に拡大・縮小して表示してくれます。
つまり、を使っています。WindowsXamlHost の子供として ProgressRing を設定し、どうにかして Viewbox のサイズをうまい事すればそれらしく表示できます。

■ コード

どうにかしてうまい事したコードです。

<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 x:Name="windowsXamlHost1" Width="300" Height="200"/>

    </Grid>
</Window>
using System;
using System.Windows;

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

            // ProgressRing 生成
            var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
            {
                IsActive = true,
                Width = 300,
                Height = 200,
                Background = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.AliceBlue),
            };

            // Viewbox 生成
            var viewbox = new Windows.UI.Xaml.Controls.Viewbox
            {
                Stretch = Windows.UI.Xaml.Media.Stretch.Uniform,
            };

            // コントロールを配置
            viewbox.Child = progressRing;
            windowsXamlHost1.Child = viewbox;

            // UWP コントロールの Loaded イベントのタイミングでは Parent が設定されている
            // ※ Window の Loaded イベントのタイミングでは未設定
            viewbox.Loaded += (sender, e) =>
            {
                // Viewbox のサイズを実サイズに調整
                var parent = ((Windows.UI.Xaml.FrameworkElement)viewbox.Parent);
                (viewbox.Width, viewbox.Height) = (parent.Width, parent.Height);
            };
        }
    }
}

f:id:rksoftware:20190114192245j:plain

■ WindowsXamlHost の Child の Parent

WindowsXamlHost の Child の Parent というと何を言っているかよくわからないかと思いますが、WindowsXamlHost の Child の Parent は試してみると動作時に Border コントロールが設定されていました。
そしてこの Border のサイズは実サイズのようです。つまり、WindowsXamlHost の Child に設定したコントロール (Viewbox) から Parent (Border) を取り出して、Viewbox を同じサイズにすればうまい事でそうです。

■ viewbox の Loaded イベント

前述のサイズ設定ですが、今のところ viewbox の Loaded イベントで行うのが良さそうです。
というのも、Window の Loaded イベント時にはまだ UWP 側のコントロール達は準備できておらず Parent プロパティが null です。Window の Loaded イベントから遅延して処理を実行する方法もありますが、Parent プロパティが null でなくなるまでの時間が一定ではなかったので筋が悪いと判断しました。

改良

□ サイズ設定方法の問題

前述のコードはそれなりに動きますが、WindowsXamlHost のサイズ設定と ProgressRing のサイズ設定をうまい事合わせ続ける必要があります。
例えば、ProgressRing のサイズを変えたいなと思って次のようにサイズを設定したとします。

// ProgressRing 生成
var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
{
    IsActive = true,
    Width = 100,
    Height = 100,
    Background = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.AliceBlue),
};

しかし Viewbox 先生は次のように構わず拡大してしまいます。 f:id:rksoftware:20190114192245j:plain

うまい事するには、WindowsXamlHost のサイズも同じように変更しなければなりませんし、逆もまた同じです。

<xamlhost:WindowsXamlHost x:Name="windowsXamlHost1" Width="100" Height="100"/>

f:id:rksoftware:20190114192325j:plain

□ 対策

Viewbox と ProgressRing の間に Grid などを挟み、挟んだ Grid などのサイズを WindowsXamlHost のサイズと同じにするコードを追加します。

// コントロールを配置
grid.Children.Add(progressRing);
viewbox.Child = grid;
windowsXamlHost1.Child = viewbox;
// UWP コントロールの Loaded イベントのタイミングでは Parent が設定されている
// ※ Window の Loaded イベントのタイミングでは未設定
viewbox.Loaded += (sender, e) =>
{
    // grid のサイズを WindowsXamlHost のサイズに調整
    (grid.Width, grid.Height) = (windowsXamlHost1.Width, windowsXamlHost1.Height);

コード全体は次の様になります。

using System;
using System.Windows;

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

            // ProgressRing 生成
            var progressRing = new Windows.UI.Xaml.Controls.ProgressRing
            {
                IsActive = true,
                Width = 100,
                Height = 100,
                Background = new Windows.UI.Xaml.Media.SolidColorBrush(Windows.UI.Colors.AliceBlue),
            };

            // Grid 生成
            var grid = new Windows.UI.Xaml.Controls.Grid { };

            // Viewbox 生成
            var viewbox = new Windows.UI.Xaml.Controls.Viewbox
            {
                Stretch = Windows.UI.Xaml.Media.Stretch.Uniform,
            };

            // コントロールを配置
            grid.Children.Add(progressRing);
            viewbox.Child = grid;
            windowsXamlHost1.Child = viewbox;

            // UWP コントロールの Loaded イベントのタイミングでは Parent が設定されている
            // ※ Window の Loaded イベントのタイミングでは未設定
            viewbox.Loaded += (sender, e) =>
            {
                // grid のサイズを WindowsXamlHost のサイズに調整
                (grid.Width, grid.Height) = (windowsXamlHost1.Width, windowsXamlHost1.Height);

                // Viewbox のサイズを実サイズに調整
                var parent = ((Windows.UI.Xaml.FrameworkElement)viewbox.Parent);
                (viewbox.Width, viewbox.Height) = (parent.Width, parent.Height);
            };
        }
   }
}

実行結果

f:id:rksoftware:20190114192346j:plain

これで、常に意図したサイズで表示されます。

DPI の変更があっても大丈夫なそうです。

  • DPI の異なるディスプレイ間をウィンドウが移動した場合
  • 実行中に表示スケールが変更された場合

など。
特に問題なく表示崩れなども起きませんでした。

秋葉原 C# もくもく会 #68 勉強会を開催しました

■ C# もくもく会

C# もくもく会 #68 を開催しました。

C# もくもく会 は東京の秋葉原で毎週木曜日に開催している .NET 系の勉強会です。
もくもく自習を基本とし、分からないことを教えあったり情報共有したりしている会です。 定期開催していますので、お時間のある時に遊びに来ていただければと思います。
ちょっと詰まった時、ネット上で聞くのははずかしいなぁ、という課題のできた時などにも思い出していただけると嬉しいです。

f:id:rksoftware:20190114125825j:plain
※写真は阿波踊りで最高のパフォーマンスを発揮するために作られた阿波踊り専用エナジードリンク AwaRise です。勉強会の開催されている秋葉原で購入できます。

■ 今週の成果発表

今週は年明けのリハビリ+新しい事をはじめだしたところでといった感じでした。

ちなみにこの勉強会ですが、実は公序良俗に反しなければどのような技術を扱っても大丈夫です。そもそも C# エンジニアが C# だけしか使わないというわけではありませんし。

■初心者歓迎

このもくもく会には、入門者の方も多くご参加いただいています。 突然 C# やらなければならなくなって途方に暮れている方、何となく C# をやってきたけど改めて見直してみたい方なども大歓迎です。
入門セミナー代わりでのご参加も歓迎です。プログラミング入門者の方も是非遊びに来てください。

特に C# で課題をお持ちでなくても是非遊びに来てください。

■ 目指す勉強会スタイル

世界一敷居の低い勉強会を目指しています。

何か聞きたいことがある場合は、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

何か共有したい情報を見つけた場合も、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

そんなスタイルでやっています。

■ 次回予定

次回は 2019/01/17 に開催予定です。

C# に関心のある方、是非遊びに来てください。