WinUI2 Xaml Island(C++/WinRT)でDragUIのドラッグプレビューを事前設定できない理由と代替案

WinUI2 の Xaml Island(C++/WinRT)でドラッグ可能な TextBlock を扱っていると、ドラッグ中にマウスポインターへ追従する「プレビュー(DragUI)」をきれいに見せたくなります。一方で「ドラッグが始まる前にプレビューを設定できないか?」という疑問もよく出ます。結論と、現実的にユーザー体験を上げる代替案・改善案をまとめます。

目次

結論:WinUI2 の DragUI は「ドラッグ開始後」にしか効かない

WinUI2(Xaml Island / C++/WinRT)で表示されるドラッグ中プレビュー(DragUI)は、ドラッグ&ドロップのセッションが開始されたタイミングで初めて意味を持つ仕組みです。そのため、ドラッグ開始前(TextBlock の生成時・初期化時・事前準備段階)に DragUI を“表示”したり、“設定して反映させる”ことはできません。

現在の実装として、DragStarting ハンドラー内で args.DragUI().SetContentFromBitmapImage(...) を呼んでプレビューを設定しているなら、それが正攻法です。DragUI のカスタマイズポイントは基本的にそこ(ドラッグ開始時)に集約されています。

なぜ「ドラッグ開始前」に設定できないのか

「ドラッグ開始前に準備しておいて、ドラッグ開始時に即表示したい」という発想は自然ですが、DragUI は UI 要素のプロパティのように“常に保持されている設定”ではなく、ドラッグ操作が開始されて初めて作られる一時的なオーバーライドとして扱われます。

イメージとしては、次のように段階が分かれています。

段階ユーザー操作アプリ側でできることDragUI の扱い
事前(ドラッグ前)ホバー、フォーカス、マウスダウン、長押し前画像の読み込み/生成、キャッシュ、ToolTip 等の表示ドラッグセッションがないため反映先が存在しない
開始(DragStarting)ドラッグが成立(しきい値を超えた移動など)DataPackage の設定、DragUI の上書きここで初めて設定が効く
ドラッグ中ポインター移動、ドロップ先へ移動基本は OS が制御(アプリは介入しにくい)開始時に指定したプレビューが表示される

つまり「ドラッグ開始前に DragUI を設定する」というのは、まだ始まっていないドラッグ操作の“専用オーバーレイ”を先に作っておくという要求になり、WinUI2 の DragUI の設計思想と合いません。結果として、DragUI を“事前に見せる”目的は別の UI で実現するのが現実的です。

前提整理:DragStarting で DragUI を設定する基本形

まずは、今やっている形を「何が正しいポイントか」明確にしておきます。ドラッグプレビューをカスタマイズする場合、DragStarting で DragUI を設定するのが基本です。

例(概念:C++/WinRT、実際の型名や名前空間はプロジェクト構成に合わせてください)

// DragStarting ハンドラー内で DragUI を設定する(基本形)
void MyPage::OnDragStarting(winrt::Windows::UI::Xaml::DragStartingEventArgs const& args)
{
    // ドロップ先へ渡すデータ(例)
    auto data = args.Data();
    data.SetText(L"store logo");

    // DragUI(ドラッグ中のプレビュー)を差し替える
    // 例:事前に用意した BitmapImage を使う
    args.DragUI().SetContentFromBitmapImage(m_previewBitmapImage);
    args.DragUI().IsContentVisible(true);
}

この形自体は正しく、ここ以外で DragUI を“反映”させる場所がありません。そこで次の発想に切り替えます。

  • 見せたいタイミングがドラッグ開始前なら、DragUI ではなく別 UI(ToolTip 等)で見せる
  • ドラッグ開始時の遅延が問題なら、画像の読み込み・生成を事前に済ませて DragStarting では“使うだけ”にする

代替案:ToolTip で「事前プレビュー」を見せる

「ドラッグ開始前にプレビューっぽい表示をしたい」という要望は、DragUI ではなく ToolTip で実現するのが一番手堅いです。ToolTip はホバー、長押し、キーボードフォーカスなどで自然に表示でき、ユーザーも“事前確認”として理解しやすいからです。

提示されている XAML 例は、そのまま有効です。

<TextBlock Text="store logo">
  <ToolTipService.ToolTip>
    <Image Source="Assets/StoreLogo.png" />
  </ToolTipService.ToolTip>
</TextBlock>

実運用では、もう少しだけ「プレビューらしさ」を整えると効果が上がります。

  • サイズ上限を付ける:巨大画像で ToolTip が画面を占有しないようにする
  • 説明テキストを添える:「ドラッグして貼り付け」などの誘導ができる
  • 表示タイミングを調整する:ホバー即表示が邪魔なら遅延を入れる(環境によりプロパティ差はありますが、アプリ側で表示制御する方が確実です)

例(サイズと説明付き)

<TextBlock Text="store logo" TextWrapping="Wrap">
  <ToolTipService.ToolTip>
    <Border Padding="8" CornerRadius="8">
      <StackPanel Spacing="6">
        <Image Source="Assets/StoreLogo.png" MaxWidth="220" MaxHeight="220" Stretch="Uniform" />
        <TextBlock Text="このアイテムはドラッグ&ドロップできます" FontSize="12" />
      </StackPanel>
    </Border>
  </ToolTipService.ToolTip>
</TextBlock>

ToolTip 方式は「DragUI を事前に設定したい」という要望の本質(ユーザーが事前に内容を視認できる状態)を満たしつつ、プラットフォームの設計とも衝突しません。

ToolTip が向くケース/向かないケース

要件ToolTip が向く別 UI の方が良い
ホバーで軽く確認したい◎—
ドラッグできることを自然に知らせたい◎(説明文と相性が良い)—
必ず事前表示したい(ホバー無しでも)△Flyout / TeachingTip / 画面内の固定プレビュー
複雑な UI(ボタンやリンク)を含めたい△(操作はさせにくい)Flyout / 専用パネル

補足:DragUI の「事前設定」は無理でも、「事前読み込み」は強力

ここからが実務的に効く改善です。DragUI 自体をドラッグ開始前に反映することはできませんが、ドラッグ開始時に使う画像を“事前に読み込んで保持しておく”ことは可能です。

ドラッグ開始時に画像のデコードやファイルアクセスが走ると、次のような体感不具合が出やすくなります。

  • ドラッグ開始の反応が一瞬遅れる
  • プレビューが一拍遅れて表示される/空のプレビューになる
  • 低スペック環境やリモートデスクトップで顕著にカクつく

対策として、ドラッグのトリガーになり得る UI が表示された時点(またはホバー時点)で、画像を読み込み・変換してキャッシュしておき、DragStarting ではキャッシュを渡すだけにします。

キャッシュ戦略の比較

戦略メリット注意点おすすめ度
BitmapImage を事前生成して保持実装が簡単デコード完了のタイミングが分かりづらい場合がある○
SoftwareBitmap を事前デコードして保持DragStarting が軽くなる/確実性が上がるメモリを食いやすい。解像度管理が必要◎
RenderTargetBitmap で UI を事前キャプチャ“その時の見た目”をプレビューにできる生成コストが高い。更新タイミング設計が必要○(用途次第)

実装例:SoftwareBitmap を事前に用意して DragStarting で即利用する

DragStarting は同期的に呼ばれることが多く、そこでファイル I/O やデコードを始めるのは避けたい場面が多いです。そこで、ページ初期化やアイテム生成時点で SoftwareBitmap を用意しておき、DragStarting で渡します。

以下は考え方の例です(プロジェクトの非同期モデルや例外処理方針に合わせて調整してください)。

// メンバーとして保持(例)
winrt::Windows::Graphics::Imaging::SoftwareBitmap m_dragPreviewBitmap{ nullptr };

// どこかの初期化タイミングで事前ロード(例:Loaded、アイテム生成時など)
winrt::Windows::Foundation::IAsyncAction MyPage::PrepareDragPreviewAsync()
{
    // 例:アプリパッケージ内の画像を読み込み(実際は StorageFile の取得方法を合わせる)
    auto file = co_await winrt::Windows::Storage::StorageFile::GetFileFromApplicationUriAsync(
        winrt::Windows::Foundation::Uri(L"ms-appx:///Assets/StoreLogo.png"));

    auto stream = co_await file.OpenReadAsync();
    auto decoder = co_await winrt::Windows::Graphics::Imaging::BitmapDecoder::CreateAsync(stream);

    // 必要に応じてサイズを落としてメモリ節約(例:220px 程度に制限)
    // ここでは簡略化し、まずはフルサイズで取得
    auto softwareBitmap = co_await decoder.GetSoftwareBitmapAsync();

    // 使いやすい PixelFormat へ変換しておくと安定しやすい
    m_dragPreviewBitmap = winrt::Windows::Graphics::Imaging::SoftwareBitmap::Convert(
        softwareBitmap,
        winrt::Windows::Graphics::Imaging::BitmapPixelFormat::Bgra8,
        winrt::Windows::Graphics::Imaging::BitmapAlphaMode::Premultiplied);
}

// DragStarting では“セットするだけ”
void MyPage::OnDragStarting(winrt::Windows::UI::Xaml::DragStartingEventArgs const& args)
{
    args.Data().SetText(L"store logo");

    if (m_dragPreviewBitmap)
    {
        args.DragUI().SetContentFromSoftwareBitmap(m_dragPreviewBitmap);
        args.DragUI().IsContentVisible(true);
    }
}

ポイントは、DragStarting の中で「画像を作る」のではなく、作ったものを渡すだけにすることです。これだけでドラッグ開始時の体感品質が大きく変わることがあります。

「事前に生成する」タイミングの設計パターン

事前ロード/事前生成は、いつやるかの設計が重要です。早すぎると無駄が増え、遅すぎるとカクつきます。おすすめのパターンを整理します。

タイミング向くケースメリット注意点
画面表示(Loaded)少数アイテム、必ず使うドラッグ開始が最速初期表示コストが増える
アイテム生成時一覧があるが数は中程度ユーザーが触る可能性の高いものを先に準備できる大量生成だとメモリ増
ホバー(PointerEntered)大量アイテム、触れるものだけ準備したい無駄が少ない初回ホバーで少し遅延が出ることがある
長押し開始など(ドラッグ直前)特定ジェスチャーでドラッグ開始する UI無駄が最小間に合わないとカクつく

大量のアイテムを並べる UI なら、「ホバー時に軽量サムネイルを生成してキャッシュ」「一定時間触られないアイテムはキャッシュを破棄」など、負荷を平準化する工夫が効きます。

より“プレビュー感”を出したい場合:ToolTip 以外の選択肢

「ホバーで小さく見せるだけでは足りない」「プレビューに加えて操作説明やショートカットも見せたい」という場合は、ToolTip 以外の UI が向きます。

  • Flyout:クリックや右クリックで確実に表示。内容もリッチにできる
  • TeachingTip:誘導・オンボーディング向き。「ここをドラッグできます」を強く伝えられる
  • 画面内の固定プレビュー領域:一覧選択に追随して右ペインにプレビュー表示など(“事前確認”体験として最も強い)

ただし、これらは「DragUI の代替」ではなく「ドラッグ前の説明/確認 UI」です。DragUI を事前に設定するのではなく、事前に見せたいものは別 UI で見せ、ドラッグ開始後の見た目は DragUI で整えるという役割分担が、破綻しにくい設計です。

よくあるハマりどころ(Xaml Island / C++/WinRT)

Xaml Island は UWP XAML をデスクトップアプリに埋め込む形になるため、通常の UWP アプリとは微妙に「初期化タイミング」「スレッド」「リソースの扱い」でつまずくことがあります。DragUI/画像関連で特に多いポイントをまとめます。

症状原因として多いもの対策
ドラッグ開始が重い/一瞬固まるDragStarting 内で画像読み込み・デコードしている事前読み込み・キャッシュに切り替える
プレビューが表示されない/真っ白になる画像が未ロード、またはライフタイムが短い生成済みオブジェクトをメンバー保持し、開始時に即セット
プレビューが巨大/小さすぎる高 DPI や元画像サイズをそのまま使っている事前にサムネイル化し、最大サイズを決めておく
複数アイテムで同じプレビューが出るキャッシュのキー設計が雑(最後に触ったものを使っている等)アイテム ID ごとにキャッシュ、または DragStarting の sender から選択

実務での落とし所:ユーザー体験を崩さない“二段構え”

「ドラッグ開始前に見せたい」と「ドラッグ中に気持ちよく追従してほしい」は、実は別の要求です。WinUI2 の仕組みに合わせるなら、次の二段構えが最も安定します。

  • ドラッグ開始前:ToolTip(または Flyout / TeachingTip / 右ペインなど)でプレビューや説明を見せる
  • ドラッグ開始後:DragStarting で DragUI をセットし、ドラッグ中プレビューを整える

そして「開始時の遅延」を消すために、DragUI に渡す画像は可能な限り事前ロード/事前生成してキャッシュしておく。これが WinUI2(Xaml Island / C++/WinRT)での現実解です。

まとめ

  • WinUI2 の DragUI(ドラッグ中プレビュー)はドラッグ開始後にしか効かないため、ドラッグ開始前に設定して反映させることはできない
  • ドラッグ開始前にプレビューを見せたいなら、ToolTip が最も自然で実装も簡単
  • 体感速度を上げたいなら、DragStarting ではなく事前読み込み/キャッシュで勝負する(SoftwareBitmap などを保持して使い回す)
  • 「事前表示」と「ドラッグ中表示」は役割が違うので、UI を分けると設計が破綻しない

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次