WPF で WebView2 を埋め込んだダイアログを作ると、タイトルバー左側に Visual Studio のアイコンが表示されてしまい「ここだけ消したい」「ボタン類はそのままにしたい」というニーズがよくあります。本記事では、WPF だけでは実現できない「タイトルバー左のアイコンだけを非表示」にするために、最小限の Win32 API を組み合わせた実用的な手順と、注意点・代替案まで詳しく解説します。
WPF だけでは「タイトルバーアイコンだけ非表示」ができない理由
まず前提として、WPF の Window クラスに「タイトルバー左のアイコンだけ非表示」というプロパティは用意されていません。したがって、XAML の設定だけで Visual Studio のアイコンを消すことはできません。
よく候補に挙がるのが WindowStyle の変更ですが、それぞれのオプションは次のような挙動になります。
| WindowStyle の値 | タイトルバー左アイコン | 最小化/最大化ボタン | 主な用途・特徴 |
|---|---|---|---|
SingleBorderWindow(既定) | 表示される | 表示される | 通常のウィンドウ枠。ダイアログやメインウィンドウに一般的 |
ToolWindow | 小さなアイコンが表示 | 基本的に非表示 | ツールウィンドウ向け。タスクバーに表示されないことも多い |
None | そもそもタイトルバーが無い | すべて無し | 完全なカスタムウィンドウ用。自前でボタンやドラッグ処理が必要 |
質問のケースでは、次の条件を同時に満たしたいはずです。
- タイトルバーは普通に表示したい(OS 標準の見た目)
- 最小化ボタン・最大化ボタン・閉じるボタンは残したい
- タイトルバー左のアプリケーションアイコンだけを非表示にしたい
しかし ToolWindow を使うと、アイコンの見た目だけでなく「最小化・最大化ボタンがなくなる」「閉じるボタンの形が変わる」という副作用が出てしまいます。つまり、WPF が提供しているプロパティでは、アイコンだけをピンポイントで消すことはできないのです。
問題の正体:WebView2 ではなく「ウィンドウフレーム」の仕様
今回の現象は WebView2 が原因ではなく、「Windows のウィンドウフレーム(非クライアント領域)」の仕様によるものです。WebView2 を埋め込んでいない普通の WPF ダイアログでも、同じようにタイトルバー左にアイコンが描画されます。
OS の立場から見ると、WPF の Window も単なる「ネイティブウィンドウ(HWND)」の一種です。アイコンの描画をコントロールしているのは Win32 のスタイルフラグやメッセージであり、WPF はその上に乗っているだけ、という構造になっています。
したがって、細かい見た目を制御したい場合は、どうしても Win32 API に少しだけお世話になる必要があります。今回の「アイコンだけ消したい」という要望は、まさにその典型的なパターンです。
Win32 API でタイトルバー左アイコンだけを非表示にする考え方
タイトルバーのアイコンは、主に次の 2 つの仕組みで決まります。
- ウィンドウに設定されたアイコン
メッセージWM_SETICONで設定される「大アイコン」「小アイコン」 - ウィンドウの拡張スタイル
WS_EX_DLGMODALFRAMEを付けると「アイコンの無いダイアログ枠」として描画される
これらを組み合わせることで、次のような動作を狙えます。
WM_SETICONを使って、大アイコン/小アイコンを「なし(ゼロ)」に設定する。GetWindowLong/SetWindowLongで拡張スタイルにWS_EX_DLGMODALFRAMEを追加する。SetWindowPosにSWP_FRAMECHANGEDを指定してフレームの再描画を促す。
こうすることで、タイトルバーのアイコンだけがきれいに消え、最小化/最大化/閉じるボタンはそのまま維持されます。ツールウィンドウ風の見た目にもならず、あくまで「普通のダイアログ枠」なのがポイントです。
| 要素 | 役割 | 今回の使い方 |
|---|---|---|
WM_SETICON | ウィンドウのアイコン(大/小)を設定 | 0 を送って「アイコンなし」にする |
WS_EX_DLGMODALFRAME | ダイアログ風の枠(アイコン無し)にする拡張スタイル | 既存の拡張スタイルに OR して付与 |
SetWindowPos(SWP_FRAMECHANGED) | 非クライアント領域の再計算・再描画 | スタイル変更後の見た目を即座に反映させる |
実装手順:WPF ダイアログのタイトルバー左アイコンを消す
ここからは、実際に WPF のダイアログでタイトルバー左の Visual Studio アイコンだけを非表示にする手順を、最小構成のコードで説明します。
XAML:ToolWindow を使わず通常のウィンドウ枠を維持する
まず、XAML 側では余計なことはせず、既定の WindowStyle のままにしておきます。WindowStyle="ToolWindow" は使いません。
<Window x:Class="SampleApp.WebViewDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
Title="サンプルダイアログ"
WindowStartupLocation="CenterScreen"
Width="620"
Height="400">
<DockPanel>
<wv2:WebView2 Name="WebView" />
</DockPanel>
</Window>
ここでは WebView2 を配置していますが、繰り返しになりますがアイコンの有無には一切関係ありません。普通のコンテンツでも同じです。
C#:Win32 API でアイコンを消すヘルパークラス
次に、タイトルバーアイコンを非表示にするためのヘルパークラスを作成します。共通で使い回せるよう、static クラスとして定義しておくと便利です。
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace SampleApp
{
public static class WindowIconHelper
{
const int GWL_EXSTYLE = -20;
const int WS_EX_DLGMODALFRAME = 0x0001;
const int SWP_NOSIZE = 0x0001;
const int SWP_NOMOVE = 0x0002;
const int SWP_NOZORDER = 0x0004;
const int SWP_FRAMECHANGED = 0x0020;
const uint WM_SETICON = 0x0080;
const int ICON_SMALL = 0;
const int ICON_BIG = 1;
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(
IntPtr hWnd,
IntPtr hWndInsertAfter,
int X,
int Y,
int cx,
int cy,
uint uFlags);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
IntPtr lParam);
/// <summary>
/// WPF Window のタイトルバー左アイコンを非表示にする
/// </summary>
public static void RemoveIcon(Window window)
{
if (window == null) throw new ArgumentNullException(nameof(window));
// HWND を取得(SourceInitialized 後で呼び出すこと)
var helper = new WindowInteropHelper(window);
IntPtr hwnd = helper.Handle;
if (hwnd == IntPtr.Zero) return;
// 既存の大アイコン/小アイコンをクリア
SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_BIG, IntPtr.Zero);
SendMessage(hwnd, WM_SETICON, (IntPtr)ICON_SMALL, IntPtr.Zero);
// 拡張スタイルに WS_EX_DLGMODALFRAME を追加
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
exStyle |= WS_EX_DLGMODALFRAME;
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
// 非クライアント領域を再描画して見た目を更新
SetWindowPos(
hwnd,
IntPtr.Zero,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
}
}
ここでは説明を簡単にするために GetWindowLong / SetWindowLong を使っていますが、64 ビット環境を厳密に考慮する場合は GetWindowLongPtr / SetWindowLongPtr を使う実装にすることもできます(WPF アプリの多くでは上記でも問題なく動作します)。
ダイアログのコードビハインドからヘルパーを呼び出す
最後に、作成したヘルパークラスを実際のダイアログで呼び出します。ポイントは「ウィンドウハンドル(HWND)が確定したタイミング」で実行することです。WPF では OnSourceInitialized をオーバーライドするのが定番です。
using System;
using System.Windows;
namespace SampleApp
{
public partial class WebViewDialog : Window
{
public WebViewDialog()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// このタイミングで HWND が確定しているので、アイコンを消す
WindowIconHelper.RemoveIcon(this);
}
}
}
これで、アプリを実行したときに「タイトルバー左の Visual Studio アイコンだけが消え」、最小化・最大化・閉じるボタンは既定のまま維持されます。WebView2 の有無にかかわらず有効なテクニックです。
コードの動きと各定数の意味を詳しく理解する
同じコードを何となくコピペするだけでも動きますが、Win32 API の意味を理解しておくと、トラブルシューティングや応用がしやすくなります。
WM_SETICON で大アイコン/小アイコンをクリアする
WM_SETICON は「ウィンドウのアイコンを設定する」ためのメッセージです。第 3 引数の wParam で大きいアイコンか小さいアイコンかを指定します。
| wParam の値 | 意味 | どこに出るアイコンか |
|---|---|---|
ICON_BIG (1) | 大アイコン | Alt+Tab のサムネイルやタスクバーなど |
ICON_SMALL (0) | 小アイコン | タイトルバー左のアイコンや Alt+Tab の小さい表示など |
ここでは両方とも IntPtr.Zero を送り、「アイコンを設定しない(=クリアする)」ことによって、ウィンドウアイコンを一度リセットしています。単に Window.Icon を設定しても、クラスアイコン側が残っているとアイコンが描画され続けることがあり、確実に消すなら WM_SETICON でゼロを送るのが確実です。
WS_EX_DLGMODALFRAME で「アイコンのないダイアログ枠」にする
WS_EX_DLGMODALFRAME は、もともと Win32 のダイアログボックスで使われる拡張スタイルです。これを付けると、アイコンを表示しないダイアログ枠として描画されるようになります。
GetWindowLong で現在の拡張スタイルを取得し、|= WS_EX_DLGMODALFRAME することで、既存のフラグを維持したまま「ダイアログ枠」の属性だけを追加しています。
SetWindowPos + SWP_FRAMECHANGED で見た目の再計算を促す
スタイルフラグを変更しただけでは、既に描画されているタイトルバーには反映されません。そこで、SetWindowPos に SWP_FRAMECHANGED を指定して OS に「非クライアント領域(タイトルバーや枠線)を再計算・再描画してくれ」と依頼します。
位置・サイズを変えたいわけではないので、SWP_NOMOVE と SWP_NOSIZE を指定し、Z オーダーも変えないために SWP_NOZORDER を指定しています。これにより「見た目だけを更新」といったイメージで適用されます。
いつ呼び出すべきか:Loaded イベントではダメ?
WPF アプリでは、ウィンドウが表示されるタイミングで処理をしたくなることが多く、つい Loaded イベントに処理を書きがちです。しかし、ウィンドウハンドル(HWND)のライフサイクルを考えると、今回のような Win32 スタイル変更は SourceInitialized のタイミングで行うのが安全です。
| タイミング | 呼び出し方法 | HWND の状態 | 今回の用途への適合性 |
|---|---|---|---|
| コンストラクター | public Window() | まだ作成されていない | NG(ハンドルが IntPtr.Zero) |
Initialized イベント | XAML の解析直後 | まだ作成されていないことが多い | 非推奨 |
SourceInitialized オーバーライド | OnSourceInitialized | HWND が確定している | 推奨 |
Loaded イベント | Loaded += ... | ほぼ確実に作成済みだが若干曖昧 | 動くこともあるが、SourceInitialized のほうが明確 |
実際には Loaded でも動くケースが多いものの、「WPF の内部挙動の違い」「ホスト環境の違い」などでタイミングが微妙に変わる可能性を考えると、ハンドル生成が保証されている SourceInitialized を使うほうが安心です。
「アイコンを消す」のではなく「差し替え」で解決できる場合
開発中に Visual Studio のアイコンが出ているのが気になっているだけで、本番環境では自社製のアイコンにしたい、というケースもあります。この場合は、あえてアイコンを消すのではなく「きちんとアイコンを設定して差し替える」ほうが自然です。
代表的な方法は次の 2 つです。
| 方法 | 設定場所 | 反映範囲 | 特徴 |
|---|---|---|---|
| アプリケーションアイコンを設定 | プロジェクトの「プロパティ」→「アプリケーション」→「アイコンとマニフェスト」 | アプリ全体 | 実行ファイルのアイコン、タスクバー、タイトルバーなどに一括反映 |
Window.Icon プロパティを設定 | 各ウィンドウの XAML またはコード | そのウィンドウのみ | ダイアログごとに異なるアイコンを設定可能 |
本番環境でアプリケーションのブランドをきちんと表現したい場合は、まずこの「差し替え」での解決を検討し、それでもなお「特定のダイアログだけはアイコンを消したい」という場合に、今回紹介した Win32 API による非表示テクニックを適用すると良いでしょう。
完全カスタムタイトルバーとの比較:どちらを選ぶべきか
タイトルバーの見た目を自由自在にしたい場合、「WindowStyle="None" + WindowChrome」という選択肢もあります。こちらは、タイトルバーや枠線をすべて自前で描画するアプローチです。
| アプローチ | 見た目の自由度 | システムボタン | 実装コスト | 向いているケース |
|---|---|---|---|---|
| 今回の Win32 API でアイコンだけ消す方法 | 低い(標準枠そのまま) | OS 標準(最小化/最大化/閉じる) | 低い(数十行のヘルパー) | 「見た目は標準のまま、アイコンだけ消したい」 |
WindowStyle="ToolWindow" | 低い(標準ツールウィンドウ) | 最小化/最大化が消えるなど制限あり | 低い(プロパティ設定だけ) | 補助的なツールウィンドウ、プロパティパネルなど |
WindowStyle="None" + WindowChrome など | 非常に高い | 自前でボタン実装が必要 | 高い(マウス操作・ヒットテストなど) | ブランド重視のカスタム UI、全画面アプリなど |
今回の要件は「標準のダイアログ風はそのままに、アイコンだけを消したい」ので、実装コストと効果のバランスを考えると、Win32 API を使ったアイコン非表示が最も現実的な選択肢になります。
再利用しやすくする工夫:添付プロパティで「隠したいウィンドウだけ」指定する
アプリケーション内にダイアログがたくさんある場合、毎回 OnSourceInitialized をオーバーライドして WindowIconHelper.RemoveIcon を呼ぶのは少し面倒です。そこで、添付プロパティ(Attached Property)を使って、XAML 上で「このウィンドウはアイコンを消す」と宣言できるようにしておくと便利です。
例えば、次のような添付プロパティを用意できます。
using System;
using System.Windows;
using System.Windows.Interop;
namespace SampleApp
{
public static class WindowBehaviors
{
public static readonly DependencyProperty HideIconProperty =
DependencyProperty.RegisterAttached(
"HideIcon",
typeof(bool),
typeof(WindowBehaviors),
new PropertyMetadata(false, OnHideIconChanged));
public static void SetHideIcon(Window target, bool value)
=> target.SetValue(HideIconProperty, value);
public static bool GetHideIcon(Window target)
=> (bool)target.GetValue(HideIconProperty);
private static void OnHideIconChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (d is not Window window) return;
if ((bool)e.NewValue)
{
// SourceInitialized でヘルパーを呼び出す
window.SourceInitialized += Window_SourceInitialized;
}
else
{
window.SourceInitialized -= Window_SourceInitialized;
}
}
private static void Window_SourceInitialized(object? sender, EventArgs e)
{
if (sender is Window w)
{
WindowIconHelper.RemoveIcon(w);
}
}
}
}
この添付プロパティを使えば、XAML 側は次のようにシンプルになります。
<Window x:Class="SampleApp.WebViewDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SampleApp"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
Title="WebView2 ダイアログ"
local:WindowBehaviors.HideIcon="True"
Width="620"
Height="400">
<DockPanel>
<wv2:WebView2 Name="WebView" />
</DockPanel>
</Window>
これで、「アイコンを隠したいウィンドウだけ」に対して簡単に指定できるようになります。ドメイン固有のダイアログベースクラスを作る代わりに、既存のウィンドウにも後付けで適用できるのが添付プロパティの利点です。
WebView2 を使うときの補足ポイント
今回のタイトルバーアイコンの問題は WebView2 とは直接関係ありませんが、実際のアプリでは WebView2 を埋め込んだダイアログで使うことが多いため、いくつか実務的な補足をしておきます。
- WebView2 の初期化タイミングと独立している
アイコンの非表示処理はSourceInitializedで完結するので、WebView2 の準備状態とは独立しています。CoreWebView2InitializationCompletedなどと絡める必要はありません。 - WebView2 の例外やクラッシュとは無関係
Win32 のスタイル変更はウィンドウフレームだけに影響するため、WebView2 のレンダリングやナビゲーションには影響しません。 - 高 DPI 環境でも基本的な挙動は同じ
タイトルバーの高さやスケーリングは変わっても、WS_EX_DLGMODALFRAMEによる「アイコン無しダイアログ」の振る舞いは同じです。
つまり、WebView2 を使っているかどうかでコードを分ける必要はなく、「WPF のウィンドウでタイトルバーアイコンだけを消したい」という共通のニーズに対して一つのヘルパーで対応できる、というイメージです。
実装時によくあるつまずきと対処法
この手の Win32 連携は、「動いたと思ったら別の環境で動かない」「条件によって見た目が変わる」といった落とし穴もあります。実際によくあるつまずきをいくつか挙げておきます。
アイコンが消えない/一瞬だけ見えてしまう
次のような原因が考えられます。
- 呼び出しタイミングが早すぎる/遅すぎる
OnSourceInitializedではなくコンストラクターやInitializedで呼んでいると、ハンドルがIntPtr.Zeroのままで処理がスキップされることがあります。 - 別のコードが後からアイコンを設定している
Window.Iconの設定処理などが後から走っている場合、一度消したアイコンが再び設定されてしまうことがあります。 - カスタムウィンドウフレームとの競合
すでに独自のWindowChromeなどでフレームをいじっていると、WS_EX_DLGMODALFRAMEの効果が見えにくくなることがあります。
まずは、最小限の WPF プロジェクトで今回のコードだけを組み込んで動作を確認し、そこから本番コードに少しずつ取り込むとトラブルシュートしやすくなります。
アプリケーションのアイコンを変えたのに Visual Studio のアイコンが出る
開発中によくある現象として、「プロジェクトのアイコンを変えたのに、デバッグ実行すると Visual Studio のアイコンのままに見える」というものがあります。これは、Visual Studio のホスティングや実行方法によって、プロセスのアイコンが VS 由来に見えているだけのことが多く、本番ビルドした EXE を直接実行するとちゃんと変更が反映されていることもあります。
このような場合、「そもそもアイコンを消す必要があるのか」「本当に邪魔なのは VS のアイコンなのか」を一度確認し、本番環境での見え方も含めて設計するのがおすすめです。
まとめ:WPF ダイアログのタイトルバー左アイコンだけを消すコツ
WPF の世界だけを見ていると、「ToolWindow を使うしかないのかな」「WindowStyle=None で完全カスタムタイトルバーにするしかないのかな」と思ってしまいがちですが、実は Win32 API をほんの少しだけ使うことで、標準の見た目を保ったままタイトルバー左のアイコンだけをきれいに消すことができます。
- WPF の
Windowだけでは「タイトルバーアイコンだけ非表示」というオプションは存在しない。 - WebView2 は無関係で、問題の本質は「ウィンドウフレーム(非クライアント領域)」の描画仕様にある。
WM_SETICONで大/小アイコンをクリアし、WS_EX_DLGMODALFRAMEを拡張スタイルに付与してSetWindowPosで再描画すると、アイコンだけを非表示にできる。- 処理は
OnSourceInitializedで実行するのが安全で、共通ヘルパーや添付プロパティにしておくと再利用しやすい。 - 「アイコンを消す」前に、「アプリケーションアイコンを正しく設定して差し替える」という選択肢も検討すると良い。
タイトルバーのアイコン一つでも、アプリケーション全体の印象や「ちゃんと作り込まれている感」は変わってきます。今回紹介した WPF + Win32 API の小さなテクニックを活用して、WebView2 を含むダイアログの見た目を、意図したとおりにコントロールしてみてください。

コメント