💄

カスタムコントロールで自由に描画する

2023/05/05に公開

はじめに

WPF で用意されていた UIElement.OnRender() が WinUI 3 では用意されていないので、WinUI 3 でカスタムコントロールを作成してもそのままでは描画ができません。

Win2D を使うことで自由な描画(オーナードロー)ができました。

サンプルプログラムのソースコードは GitHub に上げてあります。

動画で見たい方はニコニコ動画をどうぞ。

プロジェクト作成

Template Studio for WinUI を使用して新規プロジェクトを作成します。プロジェクト名は TestCustomControlDraw にしました。

プロジェクトに Win2D をインストールします。[ツール → NuGet パッケージマネージャー → ソリューションの NuGet パッケージの管理]メニューで Win2D を検索するといくつか出てきますが、今回は一番新しい「Microsoft.Maui.Graphics.Win2D.WinUI.Desktop」を使用しました。

Windows App SDK 最新化

本稿執筆時点で、Windows App SDK の安定化最新バージョンは 1.3(1.3.230331000)ですが、Template Studio for WinUI は 1.2 を使用しています。[ツール → NuGet パッケージマネージャー → ソリューションの NuGet パッケージの管理]メニューの「更新プログラム」で最新化できます。

ただし、私の環境では、最新化後にアプリをビルドして実行するとクラッシュしました。一度 Visual Studio を終了し、

TestCustomControlDraw
TestCustomControlDraw.Core

フォルダー内の bin フォルダーと obj フォルダーを削除してから再度ビルドすることで対処できました(ソリューションのクリーンではダメでした)。

カスタムコントロール作成

ソリューションの Views フォルダー内に新しい項目を追加します。

「カスタムコントロール(WinUI 3)」テンプレートではなく「クラス」テンプレートで追加します。クラス名は CustomControl1 にしました。

CustomControl1 を Grid の派生にします。

internal class CustomControl1 : Grid

理由としては、描画のために Win2D の CanvasControl を使うのですが、CanvasControl は sealed なので派生させられません。Grid の子コントロールとして CanvasControl を持つ形にします。

新しい項目追加時にカスタムコントロールテンプレートを使わなかったのもこれが理由です。カスタムコントロールテンプレートを使うと Control 用の Generic.xaml が自動生成されますが無駄になります。

Loaded イベントで CanvasControl を追加します。

private void CustomControl1Loaded(Object sender, RoutedEventArgs args)
{
    _canvasControl = new();
    _canvasControl.Draw += CanvasControlDraw;
    Children.Add(_canvasControl);
}

CanvasControl は描画が必要なタイミングで Draw イベントが生じるので、ここに好きな独自描画プログラムを書くことができます。今回はコントロールいっぱいに楕円を描きました。

private void CanvasControlDraw(CanvasControl canvasControl, CanvasDrawEventArgs args)
{
    args.DrawingSession.Clear(Colors.Black);
    Single x = (Single)ActualWidth / 2;
    Single y = (Single)ActualHeight / 2;
    args.DrawingSession.FillEllipse(x, y, x, y, Colors.Red);
}

メモリリーク防止のため、Unloaded イベントで CanvasControl を除去します。

private void CustomControl1Unloaded(Object sender, RoutedEventArgs args)
{
    _canvasControl?.RemoveFromVisualTree();
    _canvasControl = null;
}

XAML

MainPage に CustomControl1 を追加します。

<Page
    x:Class="TestCustomControlDraw.Views.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:v="using:TestCustomControlDraw.Views"
    mc:Ignorable="d">

    <Grid x:Name="ContentArea">
        <v:CustomControl1 />
    </Grid>

</Page>

サンプルプログラム

サンプルプログラム(GitHub に上げてあります)を実行すると、ウィンドウ内に楕円が描かれます。

おわりに

他の方法でオーナードローしている方などいらっしゃいましたら、コメントいただけると幸いです。

確認環境

項目 環境
OS Windows 11 Pro 22H2
Visual Studio 2022 17.5.5
Windows App SDK 1.3
Win2D Microsoft.Maui.Graphics.Win2D.WinUI.Desktop 7.0.81

主な改訂履歴

  • 2023/05/05 初版。
  • 2024/04/06 パッケージについて追記。

Discussion