はじめに
最近は、授業や会議をzoomなどのWeb会議システムで行うことが増えてきました。しかし、オンライン授業の大きな欠点は、画面で共有される板書がメモする間もなく流れてしまうことです。そんな悩みを解決すべく、登録した範囲(共有画面部分など)をワンクリックでスクショし、pdfで保存できるexeアプリをpythonで作ってみました。(※スクショの許可が出たもののみスクショしましょう)
アプリとその詳細は GitHubにありますので、使ってみてコメント(アドバイスなど)頂けたら嬉しいですー。
<GitHub版↓>
今回はその作成手順のメモとして、簡略化したフルスクリーンショットアプリを
1. グラフィック(見た目)設計
2. イベント(動作)設定
3. アイコン作成
4. exe化
の順番で記していこうと思います。
<簡略化版↓>
準備したもの
・windows10
・python 3.7.9(py2exeでexe化するなら3.7.Xまで)
・wxPython(phoenix)
・Power Point
・py2exe(インストールの詳細は後述)
#1. グラフィック設計
wxpythonでアプリの見た目を作ります。標準ライブラリに扱いやすいtkinterがありますが、wxpythonはイベント処理が多く、凝ったアプリを作れます。
基本はwx.Frame
(フレーム、骨格)の中にwx.Panel
(パネル)を敷き詰め、その上にwx.Button
(ボタン)などのウィジェットを配置します。
フレーム、パネル、各ウィジェットはほとんど共通の引数をとりますが、その中で今回使うものを取り上げます。
引数 | 説明 |
---|---|
parent | 親。ここに入れたフレームやパネルの上に自らを配置する。親が消えると子である自らも消える。 |
label / value | 自らの上に表示させる文字、自らが保持する値を設定する。ウィジェットごとにどちらが使えるか、使えないかが違う。値は[ウィジェット].GetLabel() /[ウィジェット].GetValue() で取得、[ウィジェット].SetLabel([値]) /[ウィジェット].SetValue([値]) で変更可能。 |
pos | (x, y) の形で、スクリーン、もしくはparent に対する自らのx-y座標を設定。左上の角 |
size | (w, h) の形で、自らの横幅と高さを設定。 |
pos
を使わず、wx.BoxSizer
などの"サイザー"でウィジェットを並べる方法もあります。そうすると、ユーザがウィンドウサイズを変えたときに、サイズに合わせて各パーツが移動したり拡大縮小したりするようにすることが可能です。(今回はウィンドウサイズを固定するため使いません)
具体的な使い方は下で見ていきましょう。
# coding: utf-8
import wx # wxpythonをインポート
try: # ⏋
from ctypes import windll # |アプリの解像度設定
windll.shcore.SetProcessDpiAwareness(True) # |(ピンぼけを防ぐ)
except: # |
pass # ⏌
app = wx.App() # 先頭にこれを書いてwxをスタート
# GUI設定────────────────────────────────────────────────────────────────────────────────────────────────
frame = wx.Frame(parent=None, # フレーム(外枠)を作成
title='スクショアプリ',
pos=(0, 0), # pos=(x, y)はスクリーン左上を原点としたフレーム位置のx-y座標
size=(400, 230), # size=(w, h)はフレームの幅(w)と高さ(h)
style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
# styleは任意の追加設定. 今回はウィンドウのリサイズを無効化する
panel = wx.Panel(parent=frame) # フレーム上にパネルを作成
wx.StaticText(parent=panel, label='保存先:', # パネル上に文字列を作成. posはparent(panel)上での相対座標
pos=(20, 20))
wx.TextCtrl(parent=panel, value='未選択', # パネル上にフォルダの入力欄を作成
pos=(150, 17), size=(160, -1), # sizeに"-1"を入力するとwx任せ
style=wx.TE_READONLY) # TE_READONLYで入力を無効化できる
wx.Button(parent=panel, label='...',
pos=(320, 16), size=(40, 35))
wx.StaticText(parent=panel, label='前回のスクショ:', pos=(20, 70))
wx.StaticText(parent=panel, label='―', pos=(150, 70), size=(210, -1),
style=wx.TE_CENTER) # TE_CENTERで, sizeで与えた幅の中で文字を中央に配置できる
wx.Button(parent=panel, label='スクショ',
pos=(20, 120), size=(340, 40))
# ───────────────────────────────────────────────────────────────────────────────────────────────────────
frame.Show() # 見た目ができたらフレームを表示
app.MainLoop() # イベント入力を受け付けるようにして完成
実行すると見た目が出来上がります。イベントを設定していないのでボタンを押しても無反応です。
#2. イベント設定
ボタンや入力欄にイベントを設定します。
基本は各動作ごとに処理する内容をそれぞれ関数func_1(event, [第二引数], ...)
にまとめ、[ウィジェット].Bind([イベントの種類], func_1)
で紐づけます。
イベントの種類はwx.EVT_BUTTON
(ボタンが押されたとき)や'wx.EVT_SLIDER'(スライダーの値が変わったとき)などの各ウィジェット固有のものから、wx.EVT_CLOSE
(ウィンドウが閉じたとき)など様々です。
# coding: utf-8
import os
import re
from glob import glob
from datetime import date
from PIL import ImageGrab
import wx
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(True)
except:
pass
TODAY = format(date.today()) # 今日の日付
last_pic_num = 0 # 写真番号
# イベント関数定義────────────────────────────────────────────────────────────────────────────────────────
def open_folder_dialog(event): # イベントで使う関数の第一引数には"event"と書く
"""
ダイアログで選択したフォルダのパスをfolder_txctrl内に表示し,
フォルダ内の最後の写真番号を取得, picture_infoの情報を更新する関数
"""
global last_pic_num
dlg = wx.DirDialog(parent=None, message='保存先選択') # ダイアログ設定
if dlg.ShowModal() == wx.ID_OK: # ShowModal()で起動, 選択されたら以下の処理をする↓
dirpath = dlg.GetPath() # 選択されたフォルダまでのパスを取得し,
folder_txctrl.SetValue(dirpath) # 入力欄を更新,
os.chdir(dirpath) # そのフォルダ内に移動,
all_pictures = [p for p in glob(os.path.join(dirpath, '*.png')) # 今日の日付が入った写真リストを作成
if re.search('{}_\\d'.format(TODAY), p)]
if all_pictures: # もし写真があれば,
last_pic_num = max(list(map(lambda x: int(re.findall('_(\\d+)', x)[0]), # 最後の番号を取得し,
all_pictures)))
picture_info.SetLabel(str(last_pic_num)) # 写真情報を更新する
else:
last_pic_num = 0
picture_info.SetLabel('─')
scshot_button.Enable() # スクショボタンを有効化
def screen_shot(event):
"""
画面をスクショし, 繰り上げた写真番号で保存, picture_infoの情報を更新する関数
"""
global last_pic_num
last_pic_num += 1 # 番号を一つ繰り上げ,
picture_info.SetLabel(str(last_pic_num)) # 写真情報を更新
screen_picture = ImageGrab.grab() # スクショをして,
save_name = TODAY + '_{}.png'.format(last_pic_num) # "2020-01-23_(番号)"の形式で,
screen_picture.save(save_name) # 保存
# ───────────────────────────────────────────────────────────────────────────────────────────────────────
app = wx.App()
# GUI設定────────────────────────────────────────────────────────────────────────────────────────────────
frame = wx.Frame(parent=None, title='スクショアプリ', pos=(0, 0), size=(400, 230),
style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
panel = wx.Panel(parent=frame)
wx.StaticText(parent=panel, label='保存先:', pos=(20, 20))
folder_txctrl = wx.TextCtrl(parent=panel, value='未選択', pos=(100, 17), size=(210, -1), # 後で呼び出す
style=wx.TE_READONLY)
folder_button = wx.Button(parent=panel, label='...', pos=(320, 16), size=(40, 35)) # 後で呼び出す
wx.StaticText(parent=panel, label='今日の最終スクショ番号:', pos=(20, 70))
picture_info = wx.StaticText(parent=panel, label='―', pos=(200, 70), size=(160, -1), # 後で呼び出す
style=wx.TE_CENTER)
scshot_button = wx.Button(parent=panel, label='スクショ', pos=(20, 120), size=(340, 40)) # 後で呼び出す
# ───────────────────────────────────────────────────────────────────────────────────────────────────────
scshot_button.Disable() # 保存先が選択されるまでスクショボタンは無効化しておく
# イベント設定────────────────────────────────────────────────────────────────────────────────────────────
folder_button.Bind(wx.EVT_BUTTON, open_folder_dialog) # ボタンを押すと, 第二引数の関数を実行
scshot_button.Bind(wx.EVT_BUTTON, screen_shot) # 注) 関数の最後に"()"をつけないように.
# ───────────────────────────────────────────────────────────────────────────────────────────────────────
frame.SetIcon(wx.Icon('sample_icon.ico')) # アイコン設定(指定されたパスが存在しないとエラー)
frame.Show()
app.MainLoop()
アイコンを作っていないので実行するとエラーがでますが、スクショボタンを押すと選んだフォルダに写真が保存されますね。
#3. アイコン作成(自己流)
.ico
という拡張子のファイルが必要ですが、ネット上に.png
→.ico
に変換できるサイトがありがたいことに存在するので、パワポでpng作って変換します。(パワポじゃなくても何でもいいです)
- まず大きめの図形を描いて、グループ化
- 図形をコピー→「貼り付けのオプション」→「図」
- それを縮小、トリミングして一辺3.91cmの正方形に収まるようにする
- それを「図として保存」
で.png
ができます。それをこちらの変換サイト半透過マルチアイコンfavicon.icoを作ろう!をありがたく利用させて頂くことで、.ico
に変換できます。今回は"sample_icon.ico"という名前でsample_2.py
と同じディレクトリに保存します。(wx.Icon([PATH])
に適切なパスを入れればどこでも大丈夫です)
#4. exe化
起動時間の短いpy2exeを使います。
###インストール
python 3.4までのバージョンを使うなら
pip install py2exe
python 3.5-3.7(以下3.Xとする)を使う場合は、py2exeのリリースサイトからpy2exe-0.9.3.2-cp3X-none-win_amd64.whl
(64ビット)かpy2exe-0.9.3.2-cp3X-none-win32.whl
(32ビット)をダウンロードし、
py -3.X -m pip install 'ダウンロードしたファイルのパス'
###準備
setup.py
というセットアップファイルと作らないといけません。(と言っても短い)
またしてもsample_2.py
と同じディレクトリに置きます。
from distutils.core import setup
import py2exe
option = {'compressed': 1, 'optimize': 2 , 'bundle_files': 3, 'excludes': ['email', 'numpy', 'test', 'tkinter']}
setup(
options={'py2exe': option},
windows=[{'script': 'sample_2.py', 'icon_resources': [(1, 'sample_icon.ico')]}],
zipfile='lib\\libs.zip'
)
###exe化実行
sample_2.py
、setup.py
のあるディレクトリでコマンドプロンプトを起動し、
py -3.X -m setup py2exe
をすると、フォルダ内にdist
フォルダができると思うので、.ico
ファイルをその中に入れて(もしくは記述したパスの場所に置いて)終了です!
あとは.exe
のショートカットでも作成してデスクトップ上に置きましょう!
おわりに
上の内容に加え、時間のかかるイベント処理はThreadingを使って別スレッドで処理することを学びました。処理が終わるまで入力を受け付けなくなり、アプリがフリーズしたようになります笑