1
4

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 3 years have passed since last update.

pythonで共有画面スクショexeアプリ

Last updated at Posted at 2020-09-22

はじめに

最近は、授業や会議を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などの"サイザー"でウィジェットを並べる方法もあります。そうすると、ユーザがウィンドウサイズを変えたときに、サイズに合わせて各パーツが移動したり拡大縮小したりするようにすることが可能です。(今回はウィンドウサイズを固定するため使いません)

具体的な使い方は下で見ていきましょう。

sample_1.py
# 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(ウィンドウが閉じたとき)など様々です。

sample_2.py
# 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作って変換します。(パワポじゃなくても何でもいいです)

  1. まず大きめの図形を描いて、グループ化
  2. 図形をコピー→「貼り付けのオプション」→「図」
  3. それを縮小、トリミングして一辺3.91cmの正方形に収まるようにする
  4. それを「図として保存」
    .pngができます。それをこちらの変換サイト半透過マルチアイコンfavicon.icoを作ろう!をありがたく利用させて頂くことで、.icoに変換できます。今回は"sample_icon.ico"という名前でsample_2.pyと同じディレクトリに保存します。(wx.Icon([PATH])に適切なパスを入れればどこでも大丈夫です)
"sample_2.py"を実行すると、ウィンドウ左上にアイコンが設定されているのが確認できます。

#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と同じディレクトリに置きます。

setup.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.pysetup.pyのあるディレクトリでコマンドプロンプトを起動し、

py -3.X -m setup py2exe

をすると、フォルダ内にdistフォルダができると思うので、.icoファイルをその中に入れて(もしくは記述したパスの場所に置いて)終了です!
あとは.exeのショートカットでも作成してデスクトップ上に置きましょう!

おわりに

上の内容に加え、時間のかかるイベント処理はThreadingを使って別スレッドで処理することを学びました。処理が終わるまで入力を受け付けなくなり、アプリがフリーズしたようになります笑

参考

1
4
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
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?