WPF:子ウィンドウを透明にするには?[C#/VB]:.NET TIPS
WPFアプリでは、.NET Framework 4.6でサポートされた新機能を使うことで、子ウィンドウを透明にできる。本稿ではその方法を解説する。
対象:.NET 4.6以降/Windows 8.0以降
「子ウィンドウ」といっても、WPFのウィンドウ(System.Windows名前空間のWindowクラス)のことではない。Win32 APIの、すなわちWindowsが直接管理している生粋のウィンドウの話である。以降では「ウィンドウ(WPF)」「ウィンドウ(Win32)」と表記して区別する。
WPFでウィンドウ(WPF)を透明/半透明にするのは簡単だ(「.NET TIPS:WPF:ウィンドウを透明にするには?[C#/VB]」参照)。しかし、WPFで作った子ウィンドウ(Win32)は、これまで透明にできなかったのである。それが、Windows 8.0以降と.NET Framework 4.6以降の組み合わせで可能になった。本稿ではその方法を説明する。
子ウィンドウ(Win32)が必要な場面
いろいろな場面が想定できるが、Win32と相互運用する場面でWPFのUIコントロールを最前面に表示しようとすると子ウィンドウ(Win32)が必須になる。例えば、WebBrowserコントロール(System.Windows.Controls名前空間)の手前にWPFのUIコントロールを配置したいような場合だ。そのような場合、MSDNの「WPF と Win32 の相互運用性」には「HwndHostは、同じトップレベルウィンドウの他のWPF要素の上に表示」されると記述されている。その意味するところは、WebBrowserコントロール(これはHwndHostの一つ)が必ず最前面に表示されるので、その他のWPFのUIコントロールはWebBrowserコントロールの手前に表示できないということである。
なお、HwndHostには、WindowsフォームのUIコントロールをWPFで利用するためのWindowsFormsHostコントロール(System.Windows.Forms.Integration名前空間)も含まれる。以前からWPFに取り組んできた開発者には、この問題に遭遇した人も多いのではないだろうか。
実際に問題を確認してみよう。WPFのプロジェクトを作り、次のようなXAMLコードを記述する。
<Window x:Class=……省略……
>
<Grid x:Name="rootGrid" Background="#e0e0e0">
<WebBrowser Margin="64,32" Panel.ZIndex="1"
Source="……省略(WebページのURL)……" />
<Border Background="DarkGreen" Margin="100,60" Panel.ZIndex="10" />
<TextBlock x:Name="textBlock1" Margin="4" HorizontalAlignment="Right" VerticalAlignment="Bottom"
>Imageコントロール</TextBlock>
<Grid x:Name="footPrint" HorizontalAlignment="Right" VerticalAlignment="Bottom"
Panel.ZIndex="20" Background="#4400a2e8">
<Border BorderBrush="Blue" BorderThickness="4" Opacity="0.25" />
<Image Source="……省略(画像ファイルの相対パス)……" Width="208" Height="208" Opacity="0.75"/>
</Grid>
<Button x:Name="button1" Click="Button_Click" Content="子ウィンドウ化"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="4" Padding="8,0"
/>
</Grid>
</Window>
WebBrowserコントロールの手前にBorderコントロールとGridコントロール(名前「footPrint」)を配置した。XAMLコードの記述順に奥から手前へと配置されるのだが、さらにZIndexプロパティも指定してある(数字の大きい方が手前に表示される)。
ウィンドウ(WPF)のトップレベルのGridコントロールには「rootGrid」という名前が付けてある。また、ButtonコントロールのClickイベントハンドラー「Button_Click」をコードビハインドに生成しておいてほしい(メソッド内は空のままでよい)。
なお、後に説明するコードビハインドでは、「rootGrid」の子要素になっている「footPrint」を、その親から切り離して子ウィンドウ(Win32)に移動させる。
上のコードを記述しているVisual Studio 2015のXAMLエディターは、次の画像のようになる。
上記のコードをVisual Studioで記述したところ(XAMLエディター)
WebBrowserコントロールは濃いめの灰色の部分だ。その内側の手前に緑色のBorderコントロールが配置されている。さらに手前にGridコントロール「footPrint」がある(青色の四角形と茶色の足跡の画像)。
このようにXAMLエディター上では、XAMLコードに記述した順序で表示されている。
なお、本稿で使っているのは無償のVisual Studio Community 2015である。
ところが実行してみると、次の画像のようにWebBrowserコントロールが最も手前に表示されてしまうのだ。
上記のコードを実行したところ(Windows 10)
WebBrowserコントロールには、Sourceプロパティに指定したWebページが表示されている。
WebBrowserコントロールの手前に配置されるはずの緑色のBorderコントロールは完全に隠されてしまった。Gridコントロール「footPrint」(青色の四角形と茶色の足跡の画像)も、WebBrowserコントロールの下になっている。コントロールに指定したZIndexプロパティも無視されてしまった。
これは、WebBrowserコントロールがHwndHostであり、ウィンドウ(WPF)とは独立した子ウィンドウ(Win32)になっているためだ。親ウィンドウ(WPF)の一部であるWPFのUIコントロールは、子ウィンドウ(Win32)の手前にくることはできないのである。
これを解決するには、子ウィンドウ(Win32)に勝てるのは子ウィンドウ(Win32)だけであるから、WPFのプログラム中で子ウィンドウ(Win32)を作成し、そこにWPFのコントロールを配置すればよい。それは従来から可能であった。ただ、子ウィンドウ(Win32)を透明にする手段は提供されていなかったのである。
子ウィンドウ(Win32)を透明にするには?
正確には、「WPFのコントロールを配置できる子ウィンドウ(Win32)を透明にするには?」と言うべきであろう。それは、今まではサポートされていなかったのである。
それが、Windows 8.0以降と.NET Framework 4.6以降の組み合わせで可能になった。Windows 8.0より以前のバージョンでは動作しないので、ご注意願いたい。なお、以降の説明ではVisual Studio 2015を使用する*1。
*1 Visual Studio 2012/2013でも、.NET Framework 4.6を導入し.NET Framework 4.6 Targeting Packを利用すれば開発できるはずである(筆者は確認していない)。
まず、プロジェクトの対象フレームワークを.NET Framework 4.6に変更する。既存のプロジェクトの場合は、プロジェクトのプロパティの[アプリケーション]タブで[対象のフレームワーク]を切り替える。新規にプロジェクトを作る場合は、プロジェクトを指定するダイアログの上部で.NET Frameworkのバージョンを設定する(次の画像)。
次に、マニフェストファイルを追加し、「このプログラムが動作するのはWindows 8.0以降である」と宣言する。
マニフェストファイルを追加するには、プロジェクトに新しい項目を追加するダイアログで[全般]カテゴリにある[アプリケーション マニフェスト ファイル]を指定し、ファイル名は「app.manifest」のまま、[追加]ボタンをクリックする(次の画像)。
追加したマニフェストファイルを開くと、ファイルの中ほどにWindows 8に関する<supportedOS>タグがコメントアウトされているので、そのコメントを外す(次の画像)。
もしも<supportedOS>タグを含むセクションが存在しない場合は、次のコードのようにマニフェストファイルの末尾に追記する。
<!-- 追加部分:ここから -->
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
</application>
</compatibility>
<!-- 追加部分:ここまで -->
</assembly>
マニフェストファイルに<supportedOS>タグを含むセクションが存在しない場合は、ファイル末尾の</assembly>閉じタグのすぐ上にこのように追記する。
最後にコードビハインドを編集する。ここではボタンのクリックイベントハンドラーから呼び出すようにした。透明な子ウィンドウ(Win32)を作り、そこにWPFのコントロールを配置するのだ(次のコード)。
private void Button_Click(object sender, RoutedEventArgs e)
{
button1.IsEnabled = false;
ShowChildWindow();
}
private void ShowChildWindow()
{
// このウィンドウ(WPF)のウィンドウ(Win32)ハンドルを取得する
IntPtr parentWindowHandle
= new System.Windows.Interop.WindowInteropHelper(this).Handle;
// 透明な子ウィンドウ(Win32)を作る
const int WS_CHLID = 0x40000000;
const int WS_CLIPCHILDREN = 0x02000000;
const int WS_VISIBLE = 0x10000000;
var windowParams
= new System.Windows.Interop.HwndSourceParameters(
"dotNetTipsTransparentChildWindow");
windowParams.ParentWindow = parentWindowHandle;
windowParams.WindowStyle = WS_CHLID | WS_CLIPCHILDREN | WS_VISIBLE;
windowParams.UsesPerPixelTransparency = true; // .NET 4.6で新設
windowParams.PositionX = 0;
windowParams.PositionY = 0;
windowParams.Width = (int)rootGrid.ActualWidth;
windowParams.Height = (int)rootGrid.ActualHeight;
var hwndsSrc = new System.Windows.Interop.HwndSource(windowParams);
// ↑マニフェストにWindows 8以降の宣言がないと、
// この行でSystem.ComponentModel.Win32Exceptionが発生する
// rootGridからUIコントロールを切り離し、子ウィンドウ(Win32)に移す
rootGrid.Children.Remove(footPrint);
hwndsSrc.RootVisual = footPrint;
// 動作確認用
textBlock1.Text = "子ウィンドウ化";
}
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
button1.IsEnabled = False
ShowChildWindow()
End Sub
Private Sub ShowChildWindow()
' このウィンドウ(WPF)のウィンドウ(Win32)ハンドルを取得する
Dim parentWindowHandle As IntPtr _
= New System.Windows.Interop.WindowInteropHelper(Me).Handle
' 透明な子ウィンドウ(Win32)を作る
Const WS_CHLID As Integer = &H40000000
Const WS_CLIPCHILDREN As Integer = &H02000000
Const WS_VISIBLE As Integer = &H10000000
Dim windowParams _
= New System.Windows.Interop.HwndSourceParameters(
"dotNetTipsTransparentChildWindow")
windowParams.ParentWindow = parentWindowHandle
windowParams.WindowStyle = WS_CHLID Or WS_CLIPCHILDREN Or WS_VISIBLE
windowParams.UsesPerPixelTransparency = True ' .NET 4.6で新設
windowParams.PositionX = 0
windowParams.PositionY = 0
windowParams.Width = rootGrid.ActualWidth
windowParams.Height = rootGrid.ActualHeight
Dim hwndsSrc = New System.Windows.Interop.HwndSource(windowParams)
' ↑マニフェストにWindows 8以降の宣言がないと、
' この行でSystem.ComponentModel.Win32Exceptionが発生する
' rootGridからUIコントロールを切り離し、子ウィンドウ(Win32)に移す
rootGrid.Children.Remove(footPrint)
hwndsSrc.RootVisual = footPrint
' 動作確認用
textBlock1.Text = "子ウィンドウ化"
End Sub
コードビハインドで従来と異なるところは、.NET Framework 4.6でHwndSourceParametersオブジェクト(System.Windows.Interop名前空間)に追加されたUsesPerPixelTransparencyプロパティ(太字の部分)にtrueをセットしている部分だけである。それだけで、作成された子ウィンドウ(Win32)は透明になる。
このコードに登場するUIコントロールについては、前掲したXAMLコードをご覧いただきたい。
なお、このコードでは、XAMLコードで定義したUIコントロールを子ウィンドウ(Win32)に移すという手段を採ったが、C#/VBのコードで直に生成したUIコントロールを子ウィンドウ(Win32)にセットしても構わない。
これで実行してみると次の画像のようになる。WebBrowserコントロールの手前に、半透明のコントロールが表示できた。
上記のコードを実行したところ(Windows 10)
最初の画像ではWebBrowserコントロールに隠されていたGridコントロール「footPrint」(青色の四角形と茶色の足跡の画像)が、今度はちゃんと手前に表示されている。
なお、子ウィンドウ(Win32)は親ウィンドウ(WPF)とは独立した存在なので、親ウィンドウ(WPF)をリサイズしても子ウィンドウ(Win32)のサイズは変わらない。今回の例で言うと、親ウィンドウ(WPF)をリサイズしても「footPrint」(青色の四角形と茶色の足跡の画像)の位置は変わらないのである。
まとめ
WPFで子ウィンドウ(Win32)を透明にするには、.NET Framework 4.6でHwndSourceParametersオブジェクトに追加されたUsesPerPixelTransparencyプロパティをtrueに設定すればよい。ただし、マニフェストファイルに「このプログラムが動作するのはWindows 8.0以降である」という宣言が必要である。
利用可能バージョン:.NET Framework 4.6以降
カテゴリ:WPF/XAML 処理対象:Windowコントロール
カテゴリ:WPF/XAML 処理対象:WebBrowserコントロール
カテゴリ:WPF/XAML 処理対象:WindowsFormsHostコントロール
使用ライブラリ:HwndSourceクラス(System.Windows.Interop名前空間)
関連TIPS:WPF:ウィンドウを透明にするには?[C#/VB]
関連TIPS:WPFアプリケーションでWebページを表示するには?[3.5 SP1、C#、VB]
関連TIPS:WPF/XAMLでWindowsフォームを利用するには?[3.0、3.5、VS 2008、C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.