11
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PyWebViewでBootstrapを使用したUIを作り、PyInstallerでEXEにする

Last updated at Posted at 2018-11-24

PythonのGUIをHTMLで構築するライブラリ、PyWebViewを使って、既存のHTMLやCSSを読み込み。さらにPyInstallerを使ってEXEとして使えるようにパッケージしてみます。

用意するもの

  • Windows 10 1903 Ver
  • Python 3.7.5
  • PyInstaller 3.7
  • PyWebView 3.0.2
  • pipenv 2018.10.13
  • Bootstrap(今回はBootstrap-honoka 4.1.3を利用)

なお、パッケージングの都合上、pipenvはプロジェクトフォルダに配置されていた方が都合が良いため、予め環境変数にPIPENV_VENV_IN_PROJECT=trueを設定しておきます。

作業

必要なライブラリをインストールする

とりあえず作業を開始します。適当なフォルダを作り、pipenvでPyInstallerとPyWebViewをインストールします。

mkdir pywebtest
cd pywebtest
pipenv install --python C:\Python36\Python.exe
pipenv install pywebview
pipenv install --dev pyinstaller

次に、最低限必要なフォルダを作成します

mkdir html
mkdir src

bootstrapを導入する

次に、Bootstrapを導入します。今回はnpmを使ってライブラリを導入します

npm init
... // パッケージの設定は適当で良いです
npm install --save bootstrap-honoka

GUI用のHTML一式を作成する

htmlフォルダには以下の三つのファイルを作成します。

  • html/main.html
  • html/main.css
  • html/main.js
html/main.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Test</title>
</head>
<body>
<h1>It works!</h1>
<button class="btn btn-primary">test button</button>
</body>
</html>
html/main.css
body{
  padding: 12px;
}
h1::after{
  content: " with CSS";
}
html/main.js
document.querySelector("button").addEventListener("click", function(){
  alert("It works!");
});

ここまでで大体次のような感じになっていると思います。

pywebtest
│  package-lock.json
│  package.json
│  Pipfile
│  Pipfile.lock
│  
├─.venv
│      
├─html
│      main.css
│      main.html
│      main.js
│       
├─node_modules
│  └─bootstrap-honoka
└─src
        ここにソースファイル

GUI呼び出しのためのコードを作成する

それではGUI作成のためのコードを書いてみます。注意点としては以下の二つ

  • PyWebViewは、ウィンドウを表示すると、そのスレッドを占有してしまうこと(その後に処理を行いたい場合は、スレッドを作る必要があること
  • PyWebViewにはHTMLやCSSのファイルを読み込むメソッドはないので、load_html, load_css, evaluate_jsメソッドにopenしたファイルを読み込ませる必要があること

特に気をつけるのは上です。以下のような感じで読み込んでみます。

src/main.py
import webview

def load_css(path):
  with open(path, mode="r", encoding="utf-8") as f:
    webview.load_css(f.read())

def load_js(path): 
  with open(path, mode="r", encoding="utf-8") as f:
    webview.evaluate_js(f.read())

def load_thread():
  load_html("html/main.html")
  load_css("html/main.css")
  load_css("node_modules/bootstrap-honoka/dist/css/bootstrap.min.css")
  load_js("html/main.js")

with open(path, mode="r", encoding="utf-8") as f:
  html = f.read()
window = webview.create_window(title="Test Application", html=html, width=640, height=320)
webview.start(load_thread, window)

ここまでできたら、pipenv runで実行します。

pipenv run ./src/main.py

すると、とりあえずアプリが起動し、無事にBootstrapが読み込まれた状態で、HTMLが表示されます。
image.png

なお、一件load_htmlload_cssのあとに呼び出せば処理が高速化されそうなものですが、これをやるとGUIの表示段階で無限ループに陥ってしまうようなので上手くいきません。必ずHTMLを読み込んでからCSSを読み込みようにします。

PyInstallerで実行ファイルを作る

それではPyInstallerを使ってみようと思います。以前と同様、ビルド用のPowerShellスクリプトを書いてビルドを行います。

create_exe.ps1
# ディレクトリの用意
if (Test-Path dist) {
  Remove-Item dist -Recurse | Out-Null
}
New-Item dist -ItemType Directory | Out-Null
# windllを捜索
Write-Host "> Searching WinDLL"
$windll = "c:\windows\WinSxS\x86_microsoft-windows-m..namespace-downlevel_*"
$windllpath = "."
if(Test-Path $windll){
  $item = (Get-ChildItem $windll | Sort-Object LastWriteTime)[0]
  $windllpath = '"c:\windows\WinSxS\{0}"' -f $item.Name
}
# pyinstaller
Write-Host "> Creation EXE"
pyinstaller `
  --name TestAPP `
  --windowed `
  --path $windllpath `
  --path C:/Windows/System32/downlevel `
  --specpath ./dist/ `
  --distpath ./dist/dist `
  --workpath ./dist/build `
  --add-data "../html;html" `
  --add-binary "../.venv/Lib/site-packages/webview/lib;webview/lib" `
  src\main.py
Write-Host "> Complete"

ここで気をつけるのは

  • add-binaryオプションでPyWebViewのDLLをパッケージに含めておくこと(パスも合わせておくこと)
  • add-dataオプションでhtmlファイルを配置しておくこと

無事exeが作成されたら、実行してみます。
image.png

単独のアプリとして実行できました。

同じようなHTMLでGUIを実現するライブラリは、eelというライブラリもあるのですが、そちらは既存のChromeをそのまま使う(拡張機能がインストールされていたら、それらも使える状態)ようになっていることや、そもそもPyInstallerでパッケージするとうまく動かないことから、採用できませんでした。

PyWebViewは EdgeHTMLを使うため 残念ながらTrident(IE11)を使います(いちおうEdgeHTMLへの移行予定自体はあるようですが)。PyWebViewの引数JSAPIで指定したPythonオブジェクトの戻り値などはIE11が対応していないPromiseオブジェクトですので、かなり従来のHTMLコーディングとは異なるアプローチが必要になるでしょう。

ただ、そこまで無理しなければアプリのGUIでは一応良いんじゃないでしょうか。ファイルやフォルダを開くダイアログを表示するなどのメソッドも完備していますし。

2019/10/22追記:CEFPythonを使う

PyWebViewでCEFというものが使えるようになりました。これを使えば、ブラウザエンジンをChromiumにすることができます(いちおうMSHTML(Edge)にも対応しているようですが、どうせ今後はこちらもChromiumになってしまうので…)。

こちらを使う場合は、まずcefpython3pythonnetという二つのモジュールを追加します。

pipenv install cefpython3
pipenv install pythonnet

PyInstallerでEXEを作るとき、cefpython3ディレクトリに入っているDLLが必要になるため、pyinstallerの呼び出しコードに、次のコマンドを追加します。

create_exe.ps1(pyinstallerのオプションに追加)
  --add-binary "../.venv/Lib/site-packages/cefpython3/;." `

で、最後にPyWebViewの公式にも書かれているように、webview.start()gui="cef"という引数を付け加えます。

main.py
# …
window = webview.create_window(title="Test Application", width=640, height=320)
webview.start(load_thread, window, gui="cef")

また、webview.start()メソッドの引数にdebug="true"をつけると開発者モードのウィンドウまで出せるようになります。これで開発が相当やりやすくなる。

CEFPythonの開発者ウィンドウで、JavaScriptのトレースを行う

CEFPythonを使った場合、HTMLはdata-uriの形式でChromiumに読み込まれています。そのため、この状態でコンソールからJavaScriptを追っていくのは難しい。

そんなときは、JavaScriptの冒頭で、何か適当にconsole.logしておきます。

image.png

すると、右側に行番号が表示されるので、そこからJavaScriptのソースを見ることができます。

11
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?