🔄

Pythonで壁紙を変更するGUIをtkinterで作ってみた

2025/02/22に公開

作りたいもの

tkinterで設定した壁紙一覧から好きな壁紙を選んで簡単に壁紙を変更するguiを作る

背景

pcの壁紙をかっこいい壁紙や可愛い壁紙あとちょっとえっちな壁紙とかを設定したいけど他の人には見られたくない...
でも、いちいち変更するのってめんどいよなって思ったので調べてみたらできそうだったので作ってみることにしました!

環境

  • windows10
  • python 3.7.9

できたもの


アプリ使用例
壁紙には例としてswitchに保存されてた画像を使いました

使用したライブラリ

  • ctypes
  • json
  • os
  • tkinter
    • filedialog
    • messagebox

どのライブラリも標準ライブラリなので新たにpip installする必要はありません。

ctypes

ctypes は Python のための外部関数ライブラリです。このライブラリは C と互換性のあるデータ型を提供し、動的リンク/共有ライブラリ内の関数呼び出しを可能にします。動的リンク/共有ライブラリを純粋な Python でラップするために使うことができます。(公式引用)
難しいことが書いてありますが、この記事で最も重要となるpcの壁紙を変更するためのライブラリになります。次のように記述することで壁紙を変更することができます。
ctypes.windll.user32.SystemParametersInfoW(20, 0, image_path, 3)

注意点ですが、この方法はWindowsでのみ使用できます。ちなみにWindowsの32bit版の場合は、先ほどのコードのInfoWのWをAに変更したら行けるらしいです(未検証)。
この壁紙を変更するのは、ctypesのほんの一部の使い方なので時間がある方は他にどんなことができるのか調べてみても面白いと思います!!

json

json形式で壁紙の名前と画像の絶対パスを読み書きするために使います。
初回はpython fileと同じ階層にjsonファイルが生成され、次回以降は同じ階層にあるjsonファイルを参照します。

os

ファイル、フォルダの作成や削除などファイル関係で色々できるライブラリです。
今回はjsonファイルを参照して得た絶対パスに画像が存在しているかを確認するために使用します。

tkintr

python標準ライブラリのguiツールキットになります。
細かい使い方などは他の方の記事とかを確認してください

filedialog

from tkniter import filedialogと書くと使うことができます。
filedialog.askopenfilename()というコードでpc内の任意のファイルを選択し、絶対パスを戻り値に受け取ることができます。

messagebox

from tkniter import messageboxと書くと使うことができます。
色々なメッセージボックスを表示させることができます。
今回は利用者にエラーを知らせるために使用しています。

コードの解説

全体コード

まずは全体のコードです
次に細かく説明を入れます

main.py
import ctypes
import json
import os
from tkinter import *
from tkinter import filedialog, messagebox

class WallpaperChange(Frame):
    def __init__(self, master):
        super().__init__(master)
        self.pack()
        
        self.file = {}
        try:
            with open("wallpaper_list.json", "r", encoding="utf-8") as f:
                self.file = json.load(f)
        except:
            pass
        
        self.master.geometry("200x280")
        self.master.title("wallpaper change")
        self.master.resizable(False, False)
        self.create_widget()
    
    def create_widget(self):
        self.listbox = Listbox(self.master, height=15, width=31, selectmode="single", takefocus=0)
        self.listbox.place(x=5, y=5)
        self.listbox.bind("<Return>", self.change)
        self.listbox.bind("<BackSpace>", self.delete)
        
        button = Button(self.master, text="壁紙追加", command=self.select_file)
        button.place(x=140, y=252)
        
        for name in self.file.keys():
            self.listbox.insert(END, name)
    
    def select_file(self):
        def this_destroy():
            if entry.get() not in self.file.keys():
                self.file[entry.get()] = filename
                try:
                    with open("wallpaper_list.json", "r", encoding="utf-8") as f:
                        json_list = json.load(f)
                except:
                    json_list = {}
                json_list[entry.get()] = filename
                with open("wallpaper_list.json", "w", encoding="utf-8") as f:
                    json.dump(json_list, f, indent=2, ensure_ascii=False)
                self.listbox.insert(END, entry.get())
                select_frame.destroy()
            else:
                messagebox.showerror("エラー", "この文字はすでに使用されています")
        
        filename = filedialog.askopenfilename(filetypes=[("Available file", ".png .jpg .jpeg .bmp .dib .tiff .wdp"), ("PNG", ".png"), ("JPEG", ".jpg .jpeg"), ("BITMAP", ".bmp .dib"), ("TIFF", ".tiff"), ("WDP", ".wdp")])
        if filename == "":
            return
        select_frame = Toplevel(self)
        select_frame.title("選択")
        select_frame.geometry("200x100")
        select_frame.grab_set()
        select_frame.focus_set()
        select_frame.transient(self.master)
        
        label = Label(select_frame, text="表示時のニックネームを決めてください")
        label.place(x=10, y=10)
        entry = Entry(select_frame)
        entry.insert(END, filename.split("/")[-1])
        entry.place(x=12, y=32)
        button = Button(select_frame, text="決定", command=this_destroy)
        button.place(x=160, y=30)
    
    def delete(self, event):
        self.file.pop(event.widget.get(event.widget.curselection()))
        event.widget.delete(event.widget.curselection())
        with open("wallpaper_list.json", "w", encoding="utf-8") as f:
            json.dump(self.file, f, indent=2, ensure_ascii=False)

    def change(self, event):
        image_path = self.file[event.widget.get(event.widget.curselection())]
        if os.path.isfile(image_path):
            ctypes.windll.user32.SystemParametersInfoW(20, 0, image_path, 3)
            self.master.destroy()
        else:
            messagebox.showerror("エラー", "対象ファイルが存在しないか参照出来ません。\n一度削除してもう一度壁紙を設定し直してください")

def main():
    root = Tk()
    app = WallpaperChange(master=root)
    app.mainloop()

if __name__ == "__main__":
    main()

init

self.file = {}
try:
    with open("wallpaper_list.json", "r", encoding="utf-8") as f:
        self.file = json.load(f)
except:
    pass

ここでは、このPythonファイルのカレントディレクトリにwallpaper_list.jsonというjsonファイルがある場合には格納されている自分でつけたニックネームと、その画像ファイルがある絶対パスをself.fileに辞書型で保存し、ない場合にはpassして何もしないようにしている

self.master.geometry("200x280")
self.master.title("wallpaper change")
self.master.resizable(False, False)
self.create_widget()

この4行では、geometryで表示画面の大きさを設定し、titleでタイトルを設定し、resizableで表示画面の大きさを自分自身で変更できないようにしている
最後にself.create_widget()でボタンなどのwidgtを設置する

create_widget

メインの画面に表示させるリストボックスとボタンの設置を行う
リストボックスはbindを用いてEnterを押したらchange関数で壁紙を変更し、Back Spaceを押したらdelete関数でリストボックスから削除することができるようにした

for name in self.file.keys():
            self.listbox.insert(END, name)

上記のコードでリストボックス設置後にself.fileの中身をリストボックスに反映させる処理をしている

select_file

表示画面の「壁紙追加」ボタンを押すことで呼び出される

filedialog.askopenfilename()

上記のコードによりファイルダイアログを表示し、追加したい壁紙を選択できるようにしている
その後Toplevel関数にて新たな画面を作製し、そこでニックネームをつけれるようにしている

delete

リストボックスから対象の画像を削除し、jsonファイルを書き換える

change

ctypes関数を使い壁紙を変更する

image_path = self.file[event.widget.get(event.widget.curselection())]

画像のフルパスは、辞書型のself.fileに設定した画像のニックネーム(辞書のkey)を入力し得ている

最後に

今回は、windowsの壁紙を自由に設定したものに変更できるguiを作りました
調べたところwindows以外でも違う関数を使うことで実現できそうなのでやりたい人は調べてやってみて下さい
せっかく最初考えていた通りのguiを作れたので次回はこのtkinterで作成したものをexe化して配布できるようにしていきたいと思います(ほんとはもうexe化してるけど書くのが苦手すぎて進まない...)
始めての記事で右も左もわからない状態ですが、これから何か作ったり、できるようになったら更新していこうと思いますのでもし良かったら記事の見やすさを向上させるためにも色々アドバイスくれたらうれしいです
ではまた

Discussion