WebView2 は、WPF アプリケーションに最新の Web 表示機能を組み込むためのブラウザエンジンです。
本記事では WebView2 を使って簡易的な Web ブラウザを実装し、
実際に画面に表示されているページの HTML(JavaScript 実行後の DOM)を取得・解析する方法を紹介します。
Web 表示だけでなく、リンクや画像 URL の抽出など、検証用途にも使える実装例をまとめています。
ソースコード
ファイル名:WebView2Browser.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3650.58" />
</ItemGroup>
</Project>
ファイル名:MainWindow.xaml.cs
using Microsoft.Web.WebView2.Core;
using System;
using System.IO;
using System.Windows;
using System.Windows.Input;
namespace WebView2Browser;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindow_Loaded;
}
private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var userDataFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"WebView2Browser",
"UserData"
);
Directory.CreateDirectory(userDataFolder);
var env = await CoreWebView2Environment.CreateAsync(
null,
userDataFolder
);
await WebView.EnsureCoreWebView2Async(env);
WebView.CoreWebView2.NavigationStarting += CoreWebView2_NavigationStarting;
WebView.CoreWebView2.NavigationCompleted += CoreWebView2_NavigationCompleted;
WebView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
Navigate("https://www.bing.com");
}
private void CoreWebView2_NewWindowRequested(
object? sender,
Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
{
// 新規ウィンドウを抑止
e.Handled = true;
// 同じWebViewで開く
WebView.CoreWebView2.Navigate(e.Uri);
Log($"NewWindow suppressed: {e.Uri}");
}
private void OnNavigateClick(object sender, RoutedEventArgs e)
{
Navigate(AddressBar.Text);
}
private void AddressBar_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
Navigate(AddressBar.Text);
}
}
private void Navigate(string url)
{
if (string.IsNullOrWhiteSpace(url))
return;
if (!url.StartsWith("http"))
{
url = "https://" + url;
}
try
{
WebView.CoreWebView2.Navigate(url);
}
catch (Exception ex)
{
Log($"Navigate error: {ex.Message}");
}
}
private void CoreWebView2_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)
{
AddressBar.Text = e.Uri;
Log($"Navigating: {e.Uri}");
}
// ページ移動完了
private async void CoreWebView2_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
{
if (WebView.Source != null)
{
AddressBar.Text = WebView.Source.ToString();
}
Log($"Navigation completed (Success={e.IsSuccess})");
// ★ タイトル取得
try
{
string? title = await GetPageTitleAsync();
if (!string.IsNullOrWhiteSpace(title))
{
Log($"Title: {title}");
}
}
catch (Exception ex)
{
Log($"Title fetch error: {ex.Message}");
}
}
private async Task<string?> GetPageTitleAsync()
{
if (WebView.CoreWebView2 == null)
return null;
// ExecuteScriptAsync は JSON 文字列を返す
var result = await WebView.CoreWebView2.ExecuteScriptAsync(
"document.title"
);
// "文字列" → 文字列 に戻す
return System.Text.Json.JsonSerializer.Deserialize<string>(result);
}
// ログ出力
private void Log(string message)
{
LogBox.AppendText(message);
LogBox.AppendText(Environment.NewLine);
LogBox.ScrollToEnd();
}
// ログクリア
private void OnClearLogClick(object sender, RoutedEventArgs e)
{
LogBox.Clear();
}
// ログコピー
private void OnCopyLogClick(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(LogBox.Text))
{
Clipboard.SetText(LogBox.Text);
}
}
// ページソース取得
private async void OnGetSourceClick(object sender, RoutedEventArgs e)
{
if (WebView.CoreWebView2 == null)
return;
// ★ 取得前に必ずログをクリア
LogBox.Clear();
Log("Fetching page source...");
try
{
string? html = await GetPageSourceAsync();
if (!string.IsNullOrEmpty(html))
{
LogBox.Clear();
Log(html);
}
else
{
Log("Source is empty.");
}
}
catch (Exception ex)
{
Log($"Source fetch error: {ex.Message}");
}
}
// ページソース取得の非同期メソッド
private async Task<string?> GetPageSourceAsync()
{
if (WebView.CoreWebView2 == null)
return null;
var result = await WebView.CoreWebView2.ExecuteScriptAsync(
"document.documentElement.outerHTML"
);
// ExecuteScriptAsync は JSON 文字列で返るためデシリアライズ
return System.Text.Json.JsonSerializer.Deserialize<string>(result);
}
// リンク一覧取得
private async void OnGetLinksClick(object sender, RoutedEventArgs e)
{
if (WebView.CoreWebView2 == null)
return;
// ★ 取得前にログをクリア
LogBox.Clear();
try
{
var links = await GetPageLinksAsync();
if (links != null && links.Count > 0)
{
foreach (var link in links)
{
Log(link);
}
}
}
catch (Exception ex)
{
Log(ex.Message);
}
}
// ページ内のリンク一覧を取得する非同期メソッド
private async Task<List<string>> GetPageLinksAsync()
{
var result = await WebView.CoreWebView2.ExecuteScriptAsync(@"
Array.from(document.querySelectorAll('a[href]'))
.map(a => a.href);
");
return System.Text.Json.JsonSerializer.Deserialize<List<string>>(result)
?? new List<string>();
}
// 画像一覧取得
private async void OnGetImagesClick(object sender, RoutedEventArgs e)
{
if (WebView.CoreWebView2 == null)
return;
// ★ 取得前にログをクリア
LogBox.Clear();
try
{
var images = await GetPageImagesAsync();
if (images != null && images.Count > 0)
{
foreach (var src in images)
{
Log(src);
}
}
}
catch (Exception ex)
{
Log(ex.Message);
}
}
// ページ内の画像一覧を取得する非同期メソッド
private async Task<List<string>> GetPageImagesAsync()
{
var result = await WebView.CoreWebView2.ExecuteScriptAsync(@"
Array.from(document.querySelectorAll('img[src]'))
.map(img => img.src);
");
return System.Text.Json.JsonSerializer.Deserialize<List<string>>(result)
?? new List<string>();
}
}
ファイル名:MainWindow.xaml
<Window x:Class="WebView2Browser.MainWindow"
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:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
xmlns:local="clr-namespace:WebView2Browser"
mc:Ignorable="d"
Title="WebView2 Browser" Height="700" Width="1000">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- アドレスバー -->
<DockPanel Grid.Row="0" Margin="4">
<Button Content="Go"
Width="32"
Margin="4,0"
DockPanel.Dock="Right"
Click="OnNavigateClick"/>
<TextBox x:Name="AddressBar"
Height="26"
VerticalContentAlignment="Center"
KeyDown="AddressBar_KeyDown"/>
</DockPanel>
<!-- メイン領域 -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ログエリア -->
<DockPanel Grid.Column="0" Margin="4">
<StackPanel Orientation="Horizontal"
DockPanel.Dock="Top"
Margin="0,0,0,4">
<!-- ログクリアボタン -->
<Button Content="ログクリア"
Height="28"
Click="OnClearLogClick"/>
<!-- ログコピー -->
<Button Content="ログコピー"
Height="28"
Click="OnCopyLogClick"/>
<!-- ソース取得 -->
<Button Content="ソース取得"
Height="28"
Click="OnGetSourceClick"/>
<!-- リンク一覧取得 -->
<Button Content="リンク一覧"
Height="28"
Click="OnGetLinksClick"/>
<!-- 画像一覧取得 -->
<Button Content="画像一覧"
Height="28"
Click="OnGetImagesClick"/>
</StackPanel>
<!-- ログ表示 -->
<TextBox x:Name="LogBox"
IsReadOnly="True"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto"/>
</DockPanel>
<!-- 左右の仕切り ここから-->
<GridSplitter
Grid.Column="1"
HorizontalAlignment="Stretch" />
<!-- 左右の仕切り ここまで-->
<!-- WebView2 -->
<wv2:WebView2 x:Name="WebView"
Grid.Column="2"
Margin="4"/>
</Grid>
</Grid>
</Window>
使い方
- 起動すると初期ページのbingが表示される。
- アドレスバーにURLを入力し、「Go」ボタンでページの遷移
- ページの遷移情報がログエリアに出力
- 「ログクリア」ボタンで、ログを消去します
- 「ログコピー」ボタンで、ログの内容をクリップボードへコピー
- 「ソース取得」ボタンで、表示中のページのソースコード(HTML)を取得
- 「リンク取得」ボタンで、a hrefを取得
- 「画像取得」ボタンで、img srcを取得

コメント