LoginSignup
0
2
お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

PythonとtkinterでシンプルなファイルマネージャーGUIを作る

Last updated at Posted at 2024-06-26

はじめに

この記事では、Pythonとtkinterライブラリを使用して、シンプルながら機能的なファイルマネージャーGUIアプリケーションを作成する方法を紹介します。

要件

このファイルマネージャーアプリケーションは、以下の要件を満たすように設計されています:

  1. ユーザーフレンドリーなGUIインターフェース
  2. ファイルとフォルダの階層構造の表示
  3. 基本的なファイル操作機能(新規作成、追加、削除、移動、名前変更)
  4. ファイル名による検索機能
  5. 直感的な操作(右クリックメニュー、ダブルクリックでの展開)

仕様

アプリケーションは以下の仕様に基づいて実装されています:

  1. GUI framework: tkinter
  2. プログラミング言語: Python 3.x
  3. ファイルシステム操作: os および shutil モジュール
  4. ユーザーインターフェース:
    • メインウィンドウサイズ: 800x600 ピクセル
    • ツリービュー: ファイルとフォルダの階層構造表示
    • 検索バー: 画面上部に配置
    • スクロールバー: ツリービューの右側に配置
  5. ファイル操作:
    • 新規フォルダ作成: 選択したディレクトリ内に新しいフォルダを作成
    • ファイル追加: 選択したディレクトリに外部ファイルをコピー
    • 削除: 選択したファイルまたはフォルダを削除(確認ダイアログあり)
    • 移動: 選択したファイルまたはフォルダを別の場所に移動
    • 名前変更: 選択したファイルまたはフォルダの名前を変更
  6. 検索機能:
    • 部分一致によるファイル名検索
    • 検索結果をツリービューに表示
  7. エラー処理:
    • アクセス権限エラーの処理(PermissionError)
    • 無効な操作に対するユーザーへのフィードバック

image.png

image.png
検索したとき

image.png
ファイル操作のメニュー

必要なライブラリ

このプロジェクトでは、Pythonの標準ライブラリのみを使用します:

  • tkinter: GUIの作成
  • os: ファイルシステム操作
  • shutil: 高水準のファイル操作

コードの全体像

まず、完全なコードを示します。その後、主要な部分を詳しく説明していきます。

import os
import shutil
from tkinter import *
from tkinter import ttk, filedialog, messagebox, simpledialog

class FileManagerGUI:
    def __init__(self, master):
        self.master = master
        self.master.title("ファイルマネージャー")
        self.master.geometry("800x600")

        # 検索バーの追加
        self.search_frame = Frame(self.master)
        self.search_frame.pack(side=TOP, fill=X)
        self.search_entry = Entry(self.search_frame)
        self.search_entry.pack(side=LEFT, expand=True, fill=X)
        self.search_button = Button(self.search_frame, text="検索", command=self.search_files)
        self.search_button.pack(side=RIGHT)

        self.tree = ttk.Treeview(self.master)
        self.tree.pack(side=LEFT, fill=BOTH, expand=True)

        self.scrollbar = ttk.Scrollbar(self.master, orient="vertical", command=self.tree.yview)
        self.scrollbar.pack(side=RIGHT, fill=Y)

        self.tree.configure(yscrollcommand=self.scrollbar.set)

        self.root_path = os.path.dirname(os.path.abspath(__file__))

        self.populate_tree()

        self.tree.bind("<Double-1>", self.on_double_click)
        self.tree.bind("<Button-3>", self.show_context_menu)

        # 右クリックメニューの作成
        self.context_menu = Menu(self.master, tearoff=0)
        self.context_menu.add_command(label="新規フォルダ作成", command=self.create_folder)
        self.context_menu.add_command(label="ファイル追加", command=self.add_file)
        self.context_menu.add_command(label="削除", command=self.delete_item)
        self.context_menu.add_command(label="移動", command=self.move_item)
        self.context_menu.add_command(label="名前変更", command=self.rename_item)

    def populate_tree(self, node=""):
        if node == "":
            self.tree.delete(*self.tree.get_children())
            node = self.tree.insert('', 'end', text=self.root_path, open=True)
            self.populate_tree(node)
        else:
            path = self.tree.item(node, "text")
            try:
                for item in os.listdir(path):
                    full_path = os.path.join(path, item)
                    try:
                        if os.path.isdir(full_path):
                            child = self.tree.insert(node, 'end', text=full_path, open=False)
                            self.populate_tree(child)
                        else:
                            self.tree.insert(node, 'end', text=full_path, open=False)
                    except PermissionError:
                        continue
            except PermissionError:
                pass

    def on_double_click(self, event):
        item = self.tree.selection()[0]
        path = self.tree.item(item, "text")
        if os.path.isdir(path):
            self.tree.delete(*self.tree.get_children(item))
            self.populate_tree(item)

    def show_context_menu(self, event):
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)

    def create_folder(self):
        selected_item = self.tree.selection()[0]
        parent_path = self.tree.item(selected_item, "text")
        
        if not os.path.isdir(parent_path):
            parent_path = os.path.dirname(parent_path)

        new_folder = simpledialog.askstring("新規フォルダ", "フォルダ名を入力してください:")
        if new_folder:
            new_path = os.path.join(parent_path, new_folder)
            os.makedirs(new_path, exist_ok=True)
            self.refresh_tree()

    def add_file(self):
        selected_item = self.tree.selection()[0]
        parent_path = self.tree.item(selected_item, "text")
        
        if not os.path.isdir(parent_path):
            parent_path = os.path.dirname(parent_path)

        file_path = filedialog.askopenfilename()
        if file_path:
            shutil.copy(file_path, parent_path)
            self.refresh_tree()

    def delete_item(self):
        selected_item = self.tree.selection()[0]
        path = self.tree.item(selected_item, "text")
        
        if messagebox.askyesno("削除確認", f"{path} を削除しますか?"):
            if os.path.isdir(path):
                shutil.rmtree(path)
            else:
                os.remove(path)
            self.refresh_tree()

    def move_item(self):
        selected_item = self.tree.selection()[0]
        source_path = self.tree.item(selected_item, "text")
        
        target_path = filedialog.askdirectory()
        if target_path:
            shutil.move(source_path, target_path)
            self.refresh_tree()

    def rename_item(self):
        selected_item = self.tree.selection()[0]
        old_path = self.tree.item(selected_item, "text")
        old_name = os.path.basename(old_path)
        
        new_name = simpledialog.askstring("名前変更", "新しい名前を入力してください:", initialvalue=old_name)
        if new_name:
            new_path = os.path.join(os.path.dirname(old_path), new_name)
            os.rename(old_path, new_path)
            self.refresh_tree()

    def search_files(self):
        search_term = self.search_entry.get().lower()
        self.tree.delete(*self.tree.get_children())
        self._search_recursive(self.root_path, search_term)

    def _search_recursive(self, path, search_term, parent=""):
        try:
            for item in os.listdir(path):
                full_path = os.path.join(path, item)
                if search_term in item.lower():
                    node = self.tree.insert(parent, 'end', text=full_path, open=False)
                    if os.path.isdir(full_path):
                        self._search_recursive(full_path, search_term, node)
                elif os.path.isdir(full_path):
                    self._search_recursive(full_path, search_term, parent)
        except PermissionError:
            pass

    def refresh_tree(self):
        self.populate_tree()

if __name__ == "__main__":
    root = Tk()
    app = FileManagerGUI(root)
    root.mainloop()

主要な機能の説明

1. ツリービューの構築

populate_treeメソッドは、指定されたディレクトリ(デフォルトではスクリプトの親ディレクトリ)からファイルとフォルダの階層構造を再帰的に構築します。

def populate_tree(self, node=""):
    if node == "":
        self.tree.delete(*self.tree.get_children())
        node = self.tree.insert('', 'end', text=self.root_path, open=True)
        self.populate_tree(node)
    else:
        path = self.tree.item(node, "text")
        try:
            for item in os.listdir(path):
                full_path = os.path.join(path, item)
                try:
                    if os.path.isdir(full_path):
                        child = self.tree.insert(node, 'end', text=full_path, open=False)
                        self.populate_tree(child)
                    else:
                        self.tree.insert(node, 'end', text=full_path, open=False)
                except PermissionError:
                    continue
        except PermissionError:
            pass

2. 右クリックメニュー

show_context_menuメソッドは、ツリービュー上での右クリックイベントを処理し、コンテキストメニューを表示します。

def show_context_menu(self, event):
    item = self.tree.identify_row(event.y)
    if item:
        self.tree.selection_set(item)
        self.context_menu.post(event.x_root, event.y_root)

3. ファイル操作

各ファイル操作(新規フォルダ作成、ファイル追加、削除、移動、名前変更)は、それぞれのメソッドで実装されています。例えば、rename_itemメソッドは以下のようになっています:

def rename_item(self):
    selected_item = self.tree.selection()[0]
    old_path = self.tree.item(selected_item, "text")
    old_name = os.path.basename(old_path)
    
    new_name = simpledialog.askstring("名前変更", "新しい名前を入力してください:", initialvalue=old_name)
    if new_name:
        new_path = os.path.join(os.path.dirname(old_path), new_name)
        os.rename(old_path, new_path)
        self.refresh_tree()

4. 検索機能

search_filesメソッドと_search_recursiveメソッドを組み合わせて、ファイル名の部分一致検索を実装しています。

def search_files(self):
    search_term = self.search_entry.get().lower()
    self.tree.delete(*self.tree.get_children())
    self._search_recursive(self.root_path, search_term)

def _search_recursive(self, path, search_term, parent=""):
    try:
        for item in os.listdir(path):
            full_path = os.path.join(path, item)
            if search_term in item.lower():
                node = self.tree.insert(parent, 'end', text=full_path, open=False)
                if os.path.isdir(full_path):
                    self._search_recursive(full_path, search_term, node)
            elif os.path.isdir(full_path):
                self._search_recursive(full_path, search_term, parent)
    except PermissionError:
        pass

実装の詳細

1. GUIの構築

アプリケーションのGUIは、tkinterを使用して構築されています。主要なコンポーネントは以下の通りです:

  • Tk(): メインウィンドウの作成
  • ttk.Treeview: ファイルとフォルダの階層構造表示
  • ttk.Scrollbar: ツリービューのスクロール機能
  • Entry: 検索バー
  • Button: 検索ボタン
  • Menu: 右クリックコンテキストメニュー

2. ファイルシステム操作

ファイルシステムの操作には、osモジュールとshutilモジュールを使用しています:

  • os.path: パス操作(結合、ディレクトリ名取得など)
  • os.listdir(): ディレクトリ内のファイルとフォルダのリスト取得
  • os.makedirs(): ディレクトリ作成

実装の詳細(続き)

2. ファイルシステム操作(続き)

  • os.rename(): ファイルまたはフォルダの名前変更
  • shutil.copy(): ファイルのコピー
  • shutil.move(): ファイルまたはフォルダの移動
  • shutil.rmtree(): ディレクトリとその中身の削除

3. エラー処理

アプリケーションは、主にPermissionErrorを捕捉し、アクセス権限のないファイルやフォルダを適切にスキップします。また、ユーザーの操作に対するフィードバック(確認ダイアログなど)も提供しています。

try:
    # ファイルシステム操作
except PermissionError:
    # エラーをスキップ
    pass

確認ダイアログの例:

if messagebox.askyesno("削除確認", f"{path} を削除しますか?"):
    # 削除処理

まとめ

このプログラムは、Pythonとtkinterを使用して、基本的なファイル管理機能を持つGUIアプリケーションを作成する方法を示しています。要件と仕様に基づいて設計されたこのアプリケーションは、ツリービュー、右クリックメニュー、ファイル操作、検索機能などの実装方法を学ぶ良い例となっています。

このコードをベースに、さらに機能を追加したり、UIをカスタマイズしたりすることで、より高度なファイルマネージャーを作成することができます。例えば、以下のような拡張が考えられます:

  • ファイルのプレビュー機能
  • ドラッグ&ドロップによるファイル操作
  • ファイルの詳細情報表示
  • 複数ファイルの一括操作
  • ファイルの種類別フィルタリング機能

Pythonの強力なライブラリと柔軟性を活用することで、様々なGUIアプリケーションを効率的に開発できることがわかります。

発展的なトピック

  1. マルチスレッディング: 大規模なディレクトリ構造を扱う場合、ファイル操作やツリービューの更新を別スレッドで行うことで、UIの応答性を向上させることができます。

  2. ファイルシステムイベントの監視: watchdogライブラリを使用して、ファイルシステムの変更をリアルタイムで監視し、自動的にツリービューを更新することができます。

  3. ファイルタイプアイコン: ファイルの種類に応じたアイコンを表示することで、視覚的な情報を強化できます。

  4. 設定の保存と読み込み: ユーザーの設定(ウィンドウサイズ、最後に開いたディレクトリなど)をJSONファイルに保存し、次回起動時に読み込むことで、ユーザーエクスペリエンスを向上させることができます。

注意点

  • このアプリケーションは、ユーザーのファイルシステムに直接アクセスするため、誤操作によるデータ損失のリスクがあります。重要なファイルを扱う際は十分に注意し、必要に応じてバックアップを取ることをお勧めします。

  • 大規模なディレクトリ構造を扱う場合、初期ロードに時間がかかる可能性があります。この問題は、遅延ロードや仮想化されたツリービューの実装により改善できます。

  • セキュリティ上の理由から、一部のシステムフォルダやファイルへのアクセスが制限される場合があります。アプリケーションは、これらの制限を適切に処理する必要があります。

参考リンク

以上で、PythonとtkinterによるシンプルなファイルマネージャーGUIの作成に関する解説を終わります。このプロジェクトを通じて、GUIアプリケーション開発の基礎と、Pythonを使用したファイルシステム操作の実践的な方法を学ぶことができました。さらなる改善や機能追加に挑戦して、プログラミングスキルを磨いていってください。

0
2
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
0
2