UWPでカスタムタイトルバーを実装していると、Popupを開いた途端にウィンドウがドラッグできなくなる――開発現場でよく遭遇する現象です。本記事ではその“なぜ”を、XAMLのヒットテストとレイヤー構造の観点からかみ砕いて解説し、実プロジェクトで使える回避策と実装レシピ、落とし穴チェックリスト、サンプルコードまで一気通貫でまとめます。
現象の整理と前提
まずは状況を整理します。読者の環境に照らして相違がないか確認してください。
- UWPアプリで
ExtendsContentIntoTitleBar(正確にはCoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;)を有効化し、XAMLに独自タイトルバー(例:<Grid x:Name="AppTitleBar">)を配置している。 - 通常時は、
Window.Current.SetTitleBar(AppTitleBar);によってウィンドウをドラッグできる。 - しかし、どこかで
Popupを 開いた瞬間、タイトルバーをドラッグしてもウィンドウが動かない。 - タイトルバー要素のZ順(前面/背面)を入れ替えても改善しない。
- 一方、コードビハインドで
new ApplicationTitleBar()を生成しSetTitleBarに渡すと、Popupの表示中でもドラッグできた。
根本原因 ― なぜドラッグできなくなるのか
答えは「レイヤー(描画面)とヒットテスト(入力の当たり判定)の優先順位」にあります。
UWPの Popup は、通常のページUI(メインUIツリー)とは別の最前面レイヤーに配置されます。開いている間は、そのレイヤーが当該領域のヒットテストを独占します。これにより、背後にあるXAML要素(たとえそれが SetTitleBar の対象であっても)へポインタ入力が届かなくなります。
つまり、Popup の矩形がタイトルバー領域と重なる構成だと、ポインタダウンが Popup レイヤーで消費され、OSに「ドラッグ領域を押した」という事実が伝わらないため、ウィンドウが動かないのです。
この挙動は、IsLightDismissEnabled などの有無に関わらず(外側タップ検知のために)広い当たり判定を持つケースで顕著になります。
SetTitleBar が効くとき/効かないときの違い
Window.Current.SetTitleBar(UIElement element) は、渡した UIElement の 矩形範囲を「ドラッグ可能な非クライアント領域として OS に登録」するAPIです。理屈の上では、XAMLのZ順より高い概念に置かれているため、「Popup の重なりに影響されない」ように思えます。
ところが実際には、ドラッグ開始のトリガー(ポインタダウン)は最前面レイヤーが先に受け取る仕様です。Popup が前面に居座る限り、背後のドラッグ領域にマウスダウン/タッチダウンが到達しないため、ドラッグは始まりません。
読者の環境で「XAMLにあった要素ではダメで、動的に生成した要素だと大丈夫」だったのは、多くの場合次のいずれかが成因です。
- ドラッグ領域の実体が Popup と被っていない
コードで生成したタイトルバーをルート直下の最前面に挿入し、かつPopupの配置(Placement・HorizontalOffset・VerticalOffsetなど)を見直したことで、結果的に 重なりが解消された。 - 「ドラッグ専用の透明レイヤー」を別コンテナとして登録
動的生成時に、実際の視覚要素とドラッグ領域(透明のGridなど)を分離し、SetTitleBarには 後者のみにフォーカスを当てる構造にした。これにより、視覚要素がポップアップと衝突しても、ドラッグ判定は透明レイヤーが担保した。 - ポップアップの外形(オーバーレイ)を縮めた
IsLightDismissEnabled="False"などで広域のヒットテストを抑制し、当たり判定がタイトルバーに被さらなくなった。
重要なのは、「SetTitleBar は魔法ではなく、ドラッグ開始のポインタダウンイベントが OS に届く経路は必要」という点です。Popup がその経路をふさいでいれば、結果は変わりません。
最短で効果が出る回避策
プロダクションコードに入れやすく、効果が高い順に並べます。
| 目的 | 具体策 | ポイント |
|---|---|---|
| ポップアップ中でも確実にドラッグしたい | SetTitleBar に「ドラッグ専用の透明UI」を渡す | 見た目のUIと分離。透明の Grid をウィンドウ上端に常駐させる |
| UIを変えずに衝突を避けたい | Popup の外形をタイトルバーに被せない | Placement とオフセット調整。IsLightDismissEnabled の影響に注意 |
| 高機能ポップアップを維持したい | 別ウィンドウで代替(AppWindow / 複数ビュー) | レイヤーが分かれるためヒットテスト衝突が原理的に起きない |
| XAMLだけで完結させたい | 仕様上不可 | Popup は最前面レイヤーを専有。背後のドラッグ領域に入力は届かない |
実装レシピ:堅牢なドラッグ領域を作る
1) ルートにドラッグ専用レイヤーを常駐させる
タイトルバーの見た目(ボタンやテキスト)とは別に、ドラッグ専用且つ透明の Grid を最前面に置きます。
<Page
x:Class="SampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
```
<!-- アプリのメインUI -->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 見た目のタイトルバー(ボタン・ロゴなど) -->
<Grid x:Name="AppTitleBar" Height="48" Background="#1E1E1E">
<TextBlock Text="My App" VerticalAlignment="Center" Margin="12,0"/>
<!-- ここに検索ボックス等、インタラクティブ要素があってもOK -->
</Grid>
<!-- 本文 -->
<Grid Grid.Row="1">
<Button Content="Open Popup" Click="OpenPopup_Click" />
</Grid>
</Grid>
<!-- ★ ドラッグ専用の透明レイヤー:常に最前面に常駐させる -->
<Grid x:Name="DragOverlay"
IsHitTestVisible="True"
Background="Transparent"
VerticalAlignment="Top"
Height="48"/>
<!-- Popup自体はどこにあってもOKだが、タイトルバー矩形と極力重ねない -->
<Popup x:Name="MyPopup" IsLightDismissEnabled="True">
<Border Background="#222" Padding="16" CornerRadius="8">
<StackPanel>
<TextBlock Text="Popup content"/>
<Button Content="Close" Click="ClosePopup_Click" />
</StackPanel>
</Border>
</Popup>
```
2) コードビハインドで登録する
OSに対して「ここがドラッグ領域」と明言します。AppTitleBar ではなく、DragOverlay を登録するのがポイントです。
using Windows.ApplicationModel.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
```
// タイトルバー領域へコンテンツを拡張
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
// ★ ドラッグ専用レイヤーをOSに登録
Window.Current.SetTitleBar(DragOverlay);
// システムボタンの見た目(任意)
var av = ApplicationView.GetForCurrentView().TitleBar;
av.ButtonBackgroundColor = Windows.UI.Colors.Transparent;
av.ButtonInactiveBackgroundColor = Windows.UI.Colors.Transparent;
}
private void OpenPopup_Click(object sender, RoutedEventArgs e)
{
// 例:タイトルバーに被さらない位置へ
var marginTop = DragOverlay.ActualHeight;
MyPopup.HorizontalOffset = 16;
MyPopup.VerticalOffset = marginTop + 12;
MyPopup.IsOpen = true;
}
private void ClosePopup_Click(object sender, RoutedEventArgs e)
{
MyPopup.IsOpen = false;
}
```
}
3) 「見た目」と「判定」を分離する理由
- 見た目のタイトルバーは、ボタンや検索ボックスなどのインタラクティブ要素が含まれます。これらは通常「ドラッグではなくクリック」を期待します。
- 一方、ドラッグ判定は「クリックを奪う」性質があり、混在させると体験が破綻します。
- そこで、ドラッグ判定は透明レイヤー、見た目は通常レイヤーと役割を分けると、Popup との衝突も回避しやすくなります。
Popup 側の設計で衝突を最小化する
ヒットテストを広げすぎない
IsLightDismissEnabled="True" は便利ですが、外側タップの検知のために 広域の当たり判定が走る実装になりがちです。タイトルバーの真上に広がる配置を避け、Placement を ボタン起点にする、VerticalOffset を加算して上端から浮かせるなどの工夫で衝突を抑えられます。
Flyout/TeachingTip に置き換えられないか検討
単純なメニューやヒントであれば、Popup より Flyout や TeachingTip の方が設計済みのヒットテスト挙動を持ち、タイトルバー干渉が軽微になる場合があります。根本原理は同じレイヤー構造ですが、配置が局所化されやすい分だけ影響範囲をコントロールしやすくなります。
別ウィンドウで“原理的に”解決する
「ポップアップは大きく、タイトルバーも常にドラッグ可能にしたい」— そういう要件なら、レイヤーそのものを分離するのが確実です。UWPでは複数ビュー(CoreApplication.CreateNewView())が選択肢になります。メインウィンドウと別プロセス/別ビューで UI が持てるため、ヒットテスト衝突は起きません。
var newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
var frame = new Frame();
frame.Navigate(typeof(PopupLikePage));
Window.Current.Content = frame;
Window.Current.Activate();
```
// 新規ビューもタイトルバー拡張可能
CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
newViewId = ApplicationView.GetForCurrentView().Id;
```
});
// 別ウィンドウとして表示
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
副作用として「ウィンドウ管理」のコストが上がるため、小さなヒントやメニューには向きませんが、複雑な設定パネルやツールパレットでは有効です。
開発現場の落とし穴と対処
| 落とし穴 | 症状 | 対処 |
|---|---|---|
| 見た目のバー=ドラッグ領域にしている | ボタンやテキストがドラッグを奪われる/ポップアップと競合 | ドラッグ専用の透明レイヤーを分離し、それを SetTitleBar に登録 |
| Popup の外形が広すぎる | タイトルバーに被さってドラッグ不能 | Placement・オフセット調整、あるいは IsLightDismissEnabled の見直し |
| レイアウト変化後に登録し直していない | ウィンドウサイズ変更後にドラッグ不能になる | サイズ変更や表示状態変化のタイミングで再度 SetTitleBar を呼ぶ |
| タッチ操作を過小評価 | マウスでは動くのにタッチで動かない | タッチのヒットテスト範囲が広くなることを前提に、ドラッグ領域を十分な高さに |
| 高DPI/タイトルボタン領域との重なり | 端の方だけドラッグ不安定 | 実測の DragOverlay.ActualHeight とシステムボタン幅を考慮し領域をずらす |
チェックリスト:本番前に通す7項目
- ドラッグ専用レイヤーを用意し、
SetTitleBarに登録している。 Popup表示時でも、レイヤーの矩形と被らない配置になっている。- ウィンドウサイズ変更・フルスクリーン切替後に、領域を再計算している。
- タッチ操作・高DPI環境でドラッグ帯の高さが十分(48px以上が目安)。
- インタラクティブなUI(ボタン等)がドラッグ帯の外側にレイアウトされている。
- ダイアログやフライアウト等、他コンポーネントでも衝突がないことを実機確認。
- スクリーンリーダー等のアクセシビリティでドラッグ帯が誤作動しない。
よくある質問
Q. SetTitleBar をXAMLだけで完結できますか?
A. できません。API呼び出しはコードで行う必要があり、OSへの登録は実行時に確定します。視覚の構成はXAMLでも、登録はコードという分業が最も安定します。
Q. ドラッグ帯に IsHitTestVisible="False" を付けても動きますか?
A. おすすめしません。OS登録の都合で動作するケースもありますが、入力ルーティングの前提を壊すため、将来の変更や他UIとの相互作用で破綻しやすいです。透明背景+HitTest有効が基本です。
Q. ContentDialog なら大丈夫?
A. ContentDialog は意図的にモーダルで、背後の操作(ドラッグを含む)を抑止する設計です。ドラッグできないのが正しいと考えてください。必要なら別ウィンドウ方式を検討します。
Q. 動的生成のタイトルバーだと動いて、XAMLのバーだと動かないのはなぜ?
A. 多くは重なりの偶然によるものです。動的生成では「最前面に追加された」「Popupの配置をコードで調整した」など副作用で衝突が解消されただけで、原理は同じです。再現性を持たせるには、ドラッグ専用レイヤーの常駐+Popupの配置見直しをおすすめします。
完全サンプル:堅牢なドラッグ帯+安全なポップアップ
以下は前述の要点を1ファイルで確認できるサンプルです。
<Page
x:Class="SampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
```
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="AppTitleBar" Height="48" Background="#1E1E1E">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="My App" VerticalAlignment="Center" Margin="12,0"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="Menu" Click="OpenPopup_Click" Margin="0,0,8,0"/>
<Button Content="Action"/>
</StackPanel>
</Grid>
<Grid Grid.Row="1" Background="#111">
<TextBlock Foreground="White" Text="Main Content" Margin="12"/>
</Grid>
</Grid>
<!-- ドラッグ専用 -->
<Grid x:Name="DragOverlay" IsHitTestVisible="True" Background="Transparent"
VerticalAlignment="Top" Height="48"/>
<Popup x:Name="MyPopup" IsLightDismissEnabled="True">
<Border Background="#2B2B2B" CornerRadius="8" Padding="12">
<StackPanel>
<TextBlock Foreground="White" Text="Popup Title" FontSize="18" Margin="0,0,0,8"/>
<TextBlock Foreground="Gainsboro" Text="This is a safe popup." />
<Button Content="Close" Click="ClosePopup_Click" HorizontalAlignment="Right" Margin="0,12,0,0"/>
</StackPanel>
</Border>
</Popup>
```
using Windows.ApplicationModel.Core;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace SampleApp
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.ExtendViewIntoTitleBar = true;
Window.Current.SetTitleBar(DragOverlay);
var av = ApplicationView.GetForCurrentView().TitleBar;
av.ButtonBackgroundColor = Colors.Transparent;
av.ButtonInactiveBackgroundColor = Colors.Transparent;
SizeChanged += (_, __) => UpdateDragOverlayBounds();
Loaded += (_, __) => UpdateDragOverlayBounds();
}
private void UpdateDragOverlayBounds()
{
// 高DPIでも十分な高さを確保(必要なら48~56px程度へ調整)
DragOverlay.Height = 48;
}
private void OpenPopup_Click(object sender, RoutedEventArgs e)
{
// タイトルバーに被さらないように配置
MyPopup.HorizontalOffset = 16;
MyPopup.VerticalOffset = DragOverlay.ActualHeight + 12;
MyPopup.IsOpen = true;
}
private void ClosePopup_Click(object sender, RoutedEventArgs e)
{
MyPopup.IsOpen = false;
}
}
}
アクセシビリティとキーボード操作への配慮
- ドラッグ帯は装飾目的の透明要素ですが、タブストップにしない・名前を付けない(
IsTabStop="False")ことでフォーカス遷移を汚染しないようにします。 - キーボード主体のユーザーでも、
PopupのEscクローズやTabナビゲーションが破綻しないことを確認します。 - スクリーンリーダーに不要な領域を読み上げさせないため、
AutomationProperties.IsInAccessibleTree="False"の適用を検討します。
まとめ
UWPで「Popup を開くとタイトルバーのドラッグが効かない」問題は、前面レイヤーのヒットテスト独占が原因です。SetTitleBar 自体は有効なAPIですが、「ドラッグ開始のポインタがOSに届く」経路が塞がれていれば機能できません。最短で堅牢なのは、ドラッグ専用の透明レイヤーを常駐させ、それを SetTitleBar に登録し、Popup の配置をタイトルバーと被せないように制御すること。より高度な要件では別ウィンドウへの分離がベストプラクティスです。
本記事のレシピとチェックリストを導入すれば、ポップアップ中でも快適にウィンドウを動かせる、予測可能でメンテナブルな実装に到達できます。ぜひ自プロジェクトへ組み込んで、UIの品位と生産性を一段引き上げてください。

コメント