====== CefSharp ======
===== 概要 =====
Chromiumをアプリケーションに組み込むためのフレームワークで、C#実装がCefSharpとなります。
==== Cef ====
CefはChromium Embedded Frameworkの略で、Chromiumをアプリケーションに組み込むためのフレームワークです。\\
https://bitbucket.org/chromiumembedded/cef
Cefには各言語向けのラッパーがあり、その中のC#実装がCefSharpです。\\
https://github.com/cefsharp/CefSharp
===== インストール =====
CefSharpはNuGetでインストールできるようになっている。\\
WPF対応版とWinForms対応のコンポーネントに分かれている。\\
.NET Framework 4.5.2以上が必要となる。
Any CPUには対応していないが、プラットフォームをx86かx64を自動で判別する方法がある。\\
- NuGetでCefSharp.WinFormsをインストールする
- Visual Studioを終了する。
- 対象プロジェクトのcsprojファイルの最初のProperyGroupに末尾にtrueを追加する
- Visual Studioを起動してプロジェクトを読み込む。
- Debug および Relese フォルダ内に x86 と x64 フォルダが作成される。
- 下記サイトで指定されたプログラムを組み込む。 \\ [[https://www.valuestar.work/news/archives/27|CefSharpでAnyCPU対応に苦慮した話2]]
Any CPUで32bit優先にした場合(32bit優先とはARMのWindows環境でも動作可能となる)\\
[[https://ourcodeworld.com/articles/read/173/how-to-use-cefsharp-chromium-embedded-framework-csharp-in-a-winforms-application|How to use CefSharp (chromium embedded framework c#) in a Winforms application]]
===== ブラウザ言語を変更する =====
ひらがなと漢字が混在していた場合、ひらがなのみ太いなどのフォント表示(中国語っぽい)がされてしまう。\\
原因はロケールが違うため、言語を日本語に変更する。\\
[[https://ja.stackoverflow.com/questions/12710/cefsharp-wpf%E3%81%AElocale%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B|CefSharp.Wpfのlocaleを変更する - stackoverflow]]
CefSettings settings = new CefSettings();
settings.Locale = "ja"
settings.AcceptLanguageList = "ja-JP"
Cef.Initialize(settings);
または、OSのカルチャーを取得してセットする。
CefSettings settings = new CefSettings();
settings.Locale = System.Globalization.CultureInfo.CurrentCulture.Parent.ToString();
settings.AcceptLanguageList = System.Globalization.CultureInfo.CurrentCulture.Name;
Cef.Initialize(settings);
===== debug.logを出力しない =====
LogSeverity プロパティを無効にする。
CefSettings settings = new CefSettings();
settings.LogSeverity = LogSeverity.Disable;
Cef.Initialize(settings);
===== GPUCacheやblob_storageがデスクトップに作成される =====
CefSharpを使用したアプリケーションをデスクトップのショートカットから実行した場合\\
ショートカットの作業フォルダが空だった場合、GPUCacheがblob_storageフォルダがデスクトップに作成されてしまう。\\
対応として作業フォルダを指定することで、作業フォルダ側に作成される。
ショートカットの作業フォルダ対応ではなくプログラムで対応する場合\\
カレントフォルダを設定しただけでは、GPUCacheフォルダはカレントフォルダに作成されるが、blob_storageフォルダはデスクトップに作成されてしまう。\\
対応として、CefSettings.UserDataPath にカレントフォルダを指定する必要がある。
// カレントディレクトリをアプリケーション起動パスに設定する
string appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
Directory.SetCurrentDirectory(appPath);
CefSettings settings = new CefSettings();
settings.UserDataPath = appPath;
Cef.Initialize(settings);
===== Cef.Initializeの設定は1回のみ =====
Cef.Initializeの設定は1回のみで、new ChromiumWebBrowser する前に設定する。\\
Cef.Initialize したかどうかは、Cef.IsInitialized で判断(false:未設定、true:設定済)する。\\
[[https://csharp.hotexamples.com/examples/-/CefSettings/-/php-cefsettings-class-examples.html|C# (CSharp) CefSettings Code Examples]]
===== パネル配下にブラウザをセット =====
フォームにToolboxを配置したあったので、パネルを用意して配下にブラウザをセットした。
browser = new ChromiumWebBrowser("https://www.google.co.jp/");
this.pnlWebBrowser.Controls.Add(browser);
browser.Dock = DockStyle.Fill;
===== URL(アドレス)の取得 =====
==== WinFormsの場合 ====
Addressプロパティが見当たらないため、アドレス変更イベントで変数にセットする。\\
* [[https://stackoverflow.com/questions/53456372/cef-browser-cant-find-address-property|CEF browser can't find Address property - stackoverflow]]\\
* [[https://stackoverflow.com/questions/44760073/cefsharp-how-to-get-current-url-address-c-sharp|Cefsharp how to get current URL address? c# - stackoverflow]]
this.browser= new ChromiumWebBrowser();
this.browser.AddressChanged += browser_AddressChanged;
private void browser_AddressChanged(object sender, AddressChangedEventArgs e)
{
this.CurrentAddress = e.Address;
}
==== WPFの場合 ====
Addressプロパティで取得できる。
var browser = new CefSharp.Wpf.ChromiumWebBrowser();
this.CurrentAddress = browser.Address
===== URL(アドレス)の変更 =====
==== WinFormsの場合 ====
Addressプロパティが見当たらないため、Loadメソッドで変更する。\\
[[https://stackoverflow.com/questions/30421602/how-to-change-the-url-using-cefsharp-winforms|How to change the URL using CefSharp WinForms - stackoverflow]]
this.Browser = new ChromiumWebBrowser();
this.Browser.Load("https://www.google.co.jp/")
==== WPFの場合 ====
Addressプロパティ または、Loadメソッドで変更する。\\
[[https://qiita.com/GHKEN/items/d2d8b0745ef2622a2bbc|WPFでCefSharp(Chromiumの.NET向け実装)を使う - 1]]
var browser = new ChromiumWebBrowser();
browser.Address = "https://www.google.co.jp/";
AddressプロパティとLoadメソッドでは、基本的な挙動に変わりはありませんが、ブラウザが初期化されていないときに実行すると挙動が変わる。
=== Addressを書き換えた場合 ===
ブラウザの初期化後にそのURLに遷移する
=== Loadを呼んだ場合 ===
初期化されていないので何も起こらない
===== ブラウザ読込完了時イベント =====
IEのWebブラウザコンポーネントにあった DocumentCompleted イベントの代わり。\\
[[https://stackoverflow.com/questions/41985380/cefsharp-documentcompleted|CefSharp documentcompleted - stackoverflow]]
var browser = new ChromiumWebBrowser();
browser.LoadingStateChanged += OnLoadingStateChanged;
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
if (!e.IsLoading)
{
// 読込完了処理を記述
}
}
===== HTML要素のボタンをClickする =====
Javascriptで操作させてボタンをClickさせる。\\
ExecuteScriptAsyncメソッドで、Javascriptを実行する。
var browser = new ChromiumWebBrowser();
// Enterボタンクリック用スクリプト
string clickScript = "var inputs = document.getElementsByTagName('input');" +
"for(var i = 0; i < inputs.length; i++){ " +
" if(inputs[i].getAttribute('value') == 'Enter') inputs[i].click(); " +
"}";
string jsScript = string.Format("document.getElementById('c03').value = '{0}';", txtInput.Text);
browser.ExecuteScriptAsync(jsScript + clickScript);
===== HTML要素の値を取得する =====
Javascriptで操作させて取得する。\\
ExecuteScriptAsyncメソッドで、Javascriptを実行する。\\
[[https://stackoverrun.com/ja/q/10395511|CefSharp - HTML要素の値を取得する]]
var browser = new ChromiumWebBrowser();
browser.EvaluateScriptAsync(jsScript).ContinueWith(x =>
{
var response = x.Result;
if (response.Success && response.Result != null)
{
userCodeData = response.Result.ToString();
}
});
===== キー押下の処理 =====
IEのWebブラウザコンポーネントにあった previewkeydown イベントの代わり\\
KeyboardHandleクラスを定義する。\\
* [[https://stackoverflow.com/questions/35749505/how-to-implement-keyboard-handler-cefsharp-for-shortcut-keys|How to implement keyboard handler cefSharp for shortcut keys - stackoverflow]]
* [[http://www.itdaan.com/blog/2018/05/09/634f74f2833690334223ee71638a8f2a.html|cefsharp 实现 谷歌 F12 右键Focus 功能]]
* [[https://www.bronzedigitaldevices.ca/mario/Gistlyn/raw/master/src/Gistlyn.AppWinForms/NativeHost.cs|NativeHost.cs]]
public class KeyboardHandler : IKeyboardHandler
{
private frmMain _frm;
public KeyboardHandler(frmMain frm)
{
_frm = frm;
}
public bool OnKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey)
{
return false;
}
public bool OnPreKeyEvent(IWebBrowser browserControl, IBrowser browser, KeyType type, int windowsKeyCode, int nativeKeyCode, CefEventFlags modifiers, bool isSystemKey, ref bool isKeyboardShortcut)
{
_frm.OnPreviewKeyDown(windowsKeyCode, modifiers);
return false;
}
}
今回は、Enterキー押下時のみ処理を行う。
var browser = new ChromiumWebBrowser();
// キーイベント処理登録
browser.KeyboardHandler = new KeyboardHandler(this);
public void OnPreviewKeyDown(int windowsKeyCode, CefEventFlags modifiers)
{
if (windowsKeyCode != (int)Keys.Return) return;
// Enterキー押下時の処理を記述する。
}
===== フォーカスのセット =====
何故か、browser.Focus() ではフォーカスがセットされなかったため、Win32APIのSetFocusを使用した。\\
読み込み完了時のイベントで処理している。
// フォーカスセット用
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetFocus(IntPtr hWnd);
var browser = new ChromiumWebBrowser();
browser.LoadingStateChanged += OnLoadingStateChanged;
private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
if (String.IsNullOrEmpty(this.CurrentAddress)) return;
if (e.IsLoading) return;
// 読込完了時にWebブラウザにフォーカスをセット
this.Invoke((MethodInvoker)delegate { SetFocus(browser.Handle); });
}
最初は、ブラウザにマウスクリックするとフォーカスがセットされたのでマウスクリックするプログラムを組んだが、SetFocusで出来たのでやめた。
[DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
static extern void SetCursorPos(int X, int Y);
[DllImport("USER32.dll", CallingConvention = CallingConvention.StdCall)]
static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
private void SetFocusWebBrowser()
{
// マウス位置を移動してクリックして元に戻す
this.Invoke((MethodInvoker)delegate
{
int curX = Cursor.Position.X;
int curY = Cursor.Position.Y;
var point = this.pnlWebBrowser.PointToScreen(this.pnlWebBrowser.Location);
SetCursorPos(point.X, point.Y);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
SetCursorPos(curX, curY);
});
}
===== FAQ =====
==== マウスカーソルの位置とクリックに反応する位置がずれる ====
参照:[[http://electronicobserver.blog.fc2.com/blog-entry-104.html|ブルネイ工廠電気実験部]]
=== 現象 ===
一部のHigh-DPI 環境のPCでクリックする位置が10ドット上くらいでないと反応しない現象があった。\\
EXEファイルから直接起動すると現象が発生しないが、デスクトップのショートカットやスタートアップから起動するとこの現象が発生する。
=== 対応 ===
ショートカットのプロパティの互換性タブにある「高DPIスケール設定の上書き」の「高いDPIスケールの動作を上書きします。」にチェックを付ける。\\
Windows 7では互換性タブにある「設定」の「高DPI設定では画面のスケーリングを無効にする」にチェックを付ける。
=== プログラム対応 ===
高DPI対応するために、app.manifest ファイルを追加して、dpiAwareをtrueに設定する。\\
manifest ファイルの配布は不要。\\
[[https://blogs.msdn.microsoft.com/ttanaka/2014/08/22/dpihigh-dpi-3-12503/|アプリの高DPI(High DPI)対応について 第3回 ~ マニフェストでアプリのDPI対応レベルを変更する ~]]
「dpiAware」の設定を「true」または「true/PM」または「per monitor」に設定する。\\
違いは下記参照\\
[[http://blog.itparadise.jp/?p=802|[C#][VB.NET]Windows Formアプリケーションで表示がぼやけるのを防ぐ]]
true/PM
Cef.EnableHighDPISupport()メソッドを追加する。
Cef.EnableHighDPISupport();
Cef.Initialize(setting);