Tkinterによる画面作成のポイントをサンプル付きで紹介

本ブログで紹介する自作ツールは、Python標準のGUIライブラリ「 Tkinter」を使用しています。

Tkinterは非常に機能が豊富であるため、本記事の自作ツールのGUI作成で使っているUIパーツ(テキストボックス、ボタン、コンボボックスなど)の使い方について、サンプルプログラムやスクリーンショットを使って解説しています。

また、記事の最後にはファイルやフォルダ選択のダイアログ、ちょっとだけ便利なUIパーツをクラス(ユーザーコントロール)のソースコードも掲載していますので、是非ご活用ください。

目次

はじめに

Tkinterとは?

Tkinterは、PythonでGUI(グラフィカルユーザインタフェース)アプリケーションを作成するための標準ライブラリです。

Tkinterは、オープンソースでクロスプラットフォームの「Tk GUIツールキット」をPythonで使えるようにしたもので、Pythonプログラムにウィンドウ、ボタン、テキストボックス、ラベルなどのGUI要素を追加することができます。

またTkinterは、Pythonに標準で付属しているため、追加のインストールや設定を必要とせず、Pythonコードに直接インポートして使用することができます。

Tkinterのメリット

簡単な使用方法
Tkinterは直感的で簡単に使えるAPIを提供しており、初心者でも簡単に学ぶことができます。Pythonプログラムに数行のコードを追加するだけで、基本的なウィンドウやウィジェットを作成できます。

標準ライブラリ
TkinterはPythonの標準ライブラリとして提供されているため、追加のインストールが不要です。すべての主要なプラットフォーム(Windows、macOS、Linux)でサポートされています。

豊富なドキュメントとリソース
Tkinterは広く使用されているため、インターネット上には多くのチュートリアル、ドキュメント、サンプルコードがあります。問題に直面したときにも、迅速に解決策を見つけることができます。

軽量で高速
Tkinterは非常に軽量で、基本的なGUIアプリケーションを迅速に構築するのに適しています。

Tkinterのデメリット

限られたカスタマイズ性
Tkinterは基本的なGUI機能を提供しますが、高度なカスタマイズやモダンなUIデザインには向いていません。デザインの自由度が制限されることがあります。

外観の一貫性の欠如
Tkinterアプリケーションは、プラットフォーム間で外観が一貫しないことがあります。異なるオペレーティングシステム上で異なる見た目になることがあるため、クロスプラットフォームでの一貫したユーザー体験を提供するのが難しい場合があります。

大規模アプリケーションの構築に不向き
Tkinterは小規模から中規模のアプリケーションに適していますが、大規模なGUIアプリケーションを構築する際には、他のより強力なGUIフレームワーク(例:PyQt、Kivyなど)が適している場合があります。

Tkinterの用途

Tkinterは、そのシンプルさと使いやすさから、以下のような場面で特に有用です。

学習と教育
TkinterはPythonのGUIプログラミングを学ぶための優れたツールです。学生や初心者がGUIの基本を学ぶのに最適です。

プロトタイプ作成
GUIアプリケーションのプロトタイプを迅速に作成するために、Tkinterは非常に便利です。短時間で動作するサンプルアプリケーションを構築できます。

小規模ツールとユーティリティ
シンプルなツールやユーティリティを作成する場合、Tkinterは軽量で手軽に使えるため、効率的です。例えば、データ入力フォームや計算ツールなどが考えられます。

クロスプラットフォームアプリケーション
Tkinterは、Windows、macOS、Linuxで動作するクロスプラットフォームアプリケーションを構築するのに適しています。追加の依存関係を必要とせず、複数のプラットフォームで動作する基本的なGUIアプリケーションを作成できます。

Tkinterによる画面の作り方

Tkinterは機能が多いため、本記事では基本的な機能に限定して画面の作り方を解説致します。Tkinterに関する詳細については、「Tkinter公式サイト」に掲載されているので、こちらを参考にしてください。

一番簡単な画面(Window)の作成

下記は、Tkinter で画面(Window)を表示するための最小限のプログラムになります。
mainloop() メソッドを呼び出すことで画面の制御が Tkinter に渡り、以降は画面に関する全ての操作を Tkinter が管理することになります。

import tkinter as tk

# メインウィンドウを作成
root = tk.Tk()

# ウィンドウのタイトルを設定
root.title("Tkinter 基本ウィンドウ")

# ウィンドウを表示
root.mainloop()

mainloop()を呼び出す前に、画面の縦横サイズや画面表示位置を指定します。ちなみに Tkinter には画面をモニタ中央に表示する機能が備わっていないため、モニタの表示サイズとウィンドウサイズから表示箇所を計算で求なければなりません。

画面サイズ、背景色、その他の設定

次のソースコードは、画面の背景色やサイズ、アイコンを設定し、かつ画面をモニタの中央に表示するためのサンプルです。

import tkinter as tk

# メインウィンドウを作成
root = tk.Tk()

# ウィンドウのタイトルを設定
root.title("Tkinter 基本ウィンドウ")

# ウィンドウの背景色を指定
root.configure(bg="green")

# ウィンドウサイズを指定
width=300
height=200

# アイコンの設定
root.iconbitmap(default="./Images/hoge.ico")

# 画面サイズの下限を設定
root.minsize(width=width, height=height)

# ウィンドウを最前面に表示
root.attributes("-topmost", True) 

# ウィンドウを画面中央に表示
screen_width = root.winfo_screenwidth() # モニタ横方向の表示サイズを取得
screen_height = root.winfo_screenheight() # モニタ縦報告の表示サイズを取得
x = (screen_width - width) // 2  # ウィンドウを画面中央に配置するためのX座標
y = (screen_height - height) // 2  # ウィンドウを画面中央に配置するためのY座標
root.geometry(f"{width}x{height}+{x}+{y}") # ウィンドウを画面中央に配置

# ウィンドウを表示
root.mainloop()

ウィジェットを使った画面の作成

ウィジェットとはテキストボックス、チェックボックス、コンボボックスなどのUIコンポーネントのことです。ウィジェットごとにイベントが用意されており、ウィジェットに対して操作(ボタンの場合はクリック)が行われると、command メソッドで指定した関数が呼び出されます。

import tkinter as tk

# メインウィンドウを作成
root = tk.Tk()

# ウィンドウのタイトルを設定
root.title("Tkinter 基本ウィジェット")

# ウィンドウのサイズを設定
root.geometry("300x200")

# エントリーを作成して配置
entry = tk.Entry(root)
entry.pack()

# ボタンを作成して配置
def on_button_click():
    entry.delete(0, tk.END)  # エントリーのテキストを削除
    entry.insert(0, "ボタンがクリックされました")  # 新しいテキストを挿入

button = tk.Button(root, text="クリック", command=on_button_click)
button.pack()

# ウィンドウを表示
root.mainloop()

基本ウィジェットの紹介

ラベル(Label)

ラベルは、テキストや画像を表示するためのウィジェットです。

import tkinter as tk

root = tk.Tk()
label = tk.Label(root, text="こんにちは、Tkinter!")
label.pack()
root.mainloop()

エントリー(Entry)

エントリーは、ユーザーからのテキスト入力を受け取るためのウィジェットです。

import tkinter as tk

root = tk.Tk()
entry = tk.Entry(root)
entry.pack()
root.mainloop()

テキストボックス(Text)

テキストボックスは、複数行のテキストを表示および編集するためのウィジェットです。

import tkinter as tk

root = tk.Tk()
text = tk.Text(root)
text.pack()
root.mainloop()

コンボボックス(Combobox)

コンボボックスは、ユーザーがリストから選択するか、または自分で入力することができるウィジェットです。Tkinterの ttk モジュールを使用して実装します。

import tkinter as tk
from tkinter import ttk

def on_select(event):
    selected_value = combobox.get()
    print(f"選択された値: {selected_value}")

root = tk.Tk()

# コンボボックスの作成
combobox = ttk.Combobox(root, values=["オプション1", "オプション2", "オプション3"])
combobox.set("選択してください")
combobox.pack()

# イベントバインディング
combobox.bind("<<ComboboxSelected>>", on_select)

root.mainloop()

リストボックス(Listbox)

リストボックスは、複数の項目をリストとして表示し、ユーザーが選択できるウィジェットです。

import tkinter as tk

def on_select(event):
    selection = event.widget.curselection()
    if selection:
        index = selection[0]
        selected_value = event.widget.get(index)
        print(f"選択された値: {selected_value}")

root = tk.Tk()

# リストボックスの作成
listbox = tk.Listbox(root)
items = ["アイテム1", "アイテム2", "アイテム3"]
for item in items:
    listbox.insert(tk.END, item)
listbox.pack()

# イベントバインディング
listbox.bind("<<ListboxSelect>>", on_select)

root.mainloop()

ボタン(Button)

ボタンは、クリック可能なボタンを作成するためのウィジェットです。

import tkinter as tk

def on_button_click():
    print("ボタンがクリックされました")

root = tk.Tk()
button = tk.Button(root, text="クリック", command=on_button_click)
button.pack()
root.mainloop()

チェックボックス(Checkbutton)

チェックボックスは、ユーザーがオンまたはオフの状態を選択できるウィジェットです。複数のチェックボックスを選択することも可能です。
この例では、2つのチェックボックスが作成され、ボタンをクリックすると選択状態が表示されます。

import tkinter as tk

def show_selections():
    print(f"オプション1: {'選択' if var1.get() else '未選択'}")
    print(f"オプション2: {'選択' if var2.get() else '未選択'}")

root = tk.Tk()

# チェックボックスの状態を保持するための変数
var1 = tk.IntVar()
var2 = tk.IntVar()

# チェックボックスの作成
checkbox1 = tk.Checkbutton(root, text="オプション1", variable=var1)
checkbox1.pack()

checkbox2 = tk.Checkbutton(root, text="オプション2", variable=var2)
checkbox2.pack()

# ボタンの作成と配置
button = tk.Button(root, text="選択を表示", command=show_selections)
button.pack()

root.mainloop()

ラジオボタン(Radiobutton)

ラジオボタンは、ユーザーが複数のオプション

の中から一つを選択できるウィジェットです。ラジオボタンはグループ化され、一度に一つしか選択できません。
この例では、3つのラジオボタンが作成され、ボタンをクリックすると選択されたオプションが表示されます。

import tkinter as tk

def show_selection():
    print(f"選択されたオプション: {var.get()}")

root = tk.Tk()

# ラジオボタンの状態を保持するための変数
var = tk.StringVar()
var.set("オプション1")  # 初期選択

# ラジオボタンの作成
radiobutton1 = tk.Radiobutton(root, text="オプション1", variable=var, value="オプション1")
radiobutton1.pack()

radiobutton2 = tk.Radiobutton(root, text="オプション2", variable=var, value="オプション2")
radiobutton2.pack()

radiobutton3 = tk.Radiobutton(root, text="オプション3", variable=var, value="オプション3")
radiobutton3.pack()

# ボタンの作成と配置
button = tk.Button(root, text="選択を表示", command=show_selection)
button.pack()

root.mainloop()

スライダー(Scale)

スライダーは、ユーザーが一定範囲内の値を選択できるウィジェットです。水平および垂直方向に配置することができます。
この例では、水平および垂直のスライダーが作成され、スライダーの値が変更されると選択された値が表示されます。

import tkinter as tk

def show_value(value):
    print(f"選択された値: {value}")

root = tk.Tk()

# 水平スライダーの作成
horizontal_slider = tk.Scale(root, from_=0, to=100, orient=tk.HORIZONTAL, command=show_value)
horizontal_slider.pack()

# 垂直スライダーの作成
vertical_slider = tk.Scale(root, from_=0, to=100, orient=tk.VERTICAL, command=show_value)
vertical_slider.pack()

root.mainloop()

フレーム(Frame)

フレームは、他のウィジェットをグループ化するためのコンテナウィジェットです。

import tkinter as tk

root = tk.Tk()
frame = tk.Frame(root, relief=tk.RIDGE, borderwidth=2)
frame.pack(padx=10, pady=10)
label = tk.Label(frame, text="これはフレーム内のラベルです")
label.pack()
root.mainloop()

DataFrame


TkinterでpandasのDataFrameを表示するには、ttk.Treeviewウィジェットを使用します。これにより、テーブル形式でデータを表示できます。
このコードでは、ttk.Treeviewを使用してDataFrameをテーブル形式で表示します。各列はDataFrameのカラムに対応し、各行はDataFrameの行データに対応します。

import tkinter as tk
from tkinter import ttk
import pandas as pd

# サンプルDataFrameの作成
data = {
    "名前": ["太郎", "花子", "次郎"],
    "年齢": [25, 30, 35],
    "職業": ["エンジニア", "デザイナー", "マネージャー"]
}
df = pd.DataFrame(data)

# DataFrameをTreeviewウィジェットで表示する関数
def display_dataframe(df):
    root = tk.Tk()
    root.title("DataFrameの表示")

    frame = ttk.Frame(root)
    frame.pack(fill=tk.BOTH, expand=True)

    # Treeviewウィジェットの作成
    tree = ttk.Treeview(frame, columns=list(df.columns), show='headings')
    
    # カラムヘッダーの設定
    for col in df.columns:
        tree.heading(col, text=col)
        tree.column(col, anchor=tk.CENTER)

    # データの挿入
    for index, row in df.iterrows():
        tree.insert("", tk.END, values=list(row))

    # スクロールバーの追加
    scrollbar = ttk.Scrollbar(frame, orient=tk.VERTICAL, command=tree.yview)
    tree.configure(yscroll=scrollbar.set)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    
    tree.pack(fill=tk.BOTH, expand=True)
    root.mainloop()

# DataFrameの表示
display_dataframe(df)

Tree

ttk.Treeviewウィジェットを使用することで、エクスプローラのような階層構造の表示が可能です。
このコードでは、ttk.Treeviewを使用して階層構造のデータを表示します。tree_dataディクショナリを再帰的に処理して、各レベルのノードを挿入します。

import tkinter as tk
from tkinter import ttk

# データの作成
tree_data = {
    "ルート1": {
        "子1-1": ["孫1-1-1", "孫1-1-2"],
        "子1-2": ["孫1-2-1"]
    },
    "ルート2": {
        "子2-1": [],
        "子2-2": ["孫2-2-1", "孫2-2-2"]
    }
}

# Treeviewウィジェットの作成
def create_tree_view(tree_data):
    root = tk.Tk()
    root.title("Treeviewの例")

    frame = ttk.Frame(root)
    frame.pack(fill=tk.BOTH, expand=True)

    tree = ttk.Treeview(frame)
    tree.pack(fill=tk.BOTH, expand=True)

    def insert_items(parent, data):
        for key, value in data.items():
            node = tree.insert(parent, tk.END, text=key)
            if isinstance(value, dict):
                insert_items(node, value)
            elif isinstance(value, list):
                for item in value:
                    tree.insert(node, tk.END, text=item)

    insert_items("", tree_data)
    
    root.mainloop()

# Treeviewの作成
create_tree_view(tree_data)

メニューバー(Menu bar)

Tkinterでは、簡単にメニューバーを作成してアプリケーションに追加できます。以下の例では、メニューバーを作成し、「ファイル」メニューに「新規」、「開く」、「保存」、および「終了」の項目を追加します。

import tkinter as tk
from tkinter import filedialog, messagebox

# メニューコールバック関数
def new_file():
    print("新規ファイル作成")

def open_file():
    file_path = filedialog.askopenfilename()
    if file_path:
        print(f"開いたファイル: {file_path}")

def save_file():
    file_path = filedialog.asksaveasfilename()
    if file_path:
        print(f"保存したファイル: {file_path}")

def exit_app():
    root.quit()

# メインウィンドウの作成
root = tk.Tk()
root.title("メニューとダイアログボックスの例")

# メニューバーの作成
menubar = tk.Menu(root)

# ファイルメニューの作成
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="新規", command=new_file)
filemenu.add_command(label="開く", command=open_file)
filemenu.add_command(label="保存", command=save_file)
filemenu.add_separator()
filemenu.add_command(label="終了", command=exit_app)

# メニューバーにファイルメニューを追加
menubar.add_cascade(label="ファイル", menu=filemenu)

# メインウィンドウにメニューバーを設定
root.config(menu=menubar)

root.mainloop()

ファイルダイアログ

ファイルダイアログを使用すると、ユーザーがファイルを選択または保存できるようになります。以下は、filedialogモジュールを使用してファイルを開くおよび保存するダイアログを表示する例です。

import tkinter as tk
from tkinter import filedialog

# ファイルを開くための関数
def open_file():
    file_path = filedialog.askopenfilename()
    if file_path:
        print(f"開いたファイル: {file_path}")

# ファイルを保存するための関数
def save_file():
    file_path = filedialog.asksaveasfilename()
    if file_path:
        print(f"保存したファイル: {file_path}")

# Tkinterのルートウィンドウの作成
root = tk.Tk()
root.withdraw()  # ルートウィンドウを非表示にする

# ファイルを開く関数の呼び出し
open_file()

# ファイルを保存する関数の呼び出し
save_file()

# Tkinterのイベントループの開始
root.mainloop()

メッセージボックス

メッセージボックスは、ユーザーに情報を表示したり、確認を求めるために使用されます。以下は、messageboxモジュールを使用してメッセージボックスを表示する例です。

import tkinter as tk
from tkinter import messagebox

def show_info():
    messagebox.showinfo("情報", "これは情報メッセージです。")

def show_warning():
    messagebox.showwarning("警告", "これは警告メッセージです。")

def show_error():
    messagebox.showerror("エラー", "これはエラーメッセージです。")

def ask_question():
    response = messagebox.askquestion("質問", "続行しますか?")
    print(f"ユーザーの回答: {response}")

root = tk.Tk()

button1 = tk.Button(root,text="情報メッセージ",command=show_info)
button1.pack(padx=10,pady=10)
button2 = tk.Button(root,text="警告メッセージ",command=show_warning)
button2.pack(padx=10,pady=10)
button3 = tk.Button(root,text="エラーメッセージ",command=show_error)
button3.pack(padx=10,pady=10)
button4 = tk.Button(root,text="質問ッセージ",command=ask_question)
button4.pack(padx=10,pady=10)

root.mainloop()

イベント処理

Tkinterでは、特定のイベントが発生したときに関数を呼び出すことができます。イベントと関数を関連づけることを「イベントバインディング」と呼びます。

ボタンのクリックイベント

ボタンがクリックされた時に発生するイベントで、ボタンウィジェット専用です。

import tkinter as tk

def on_button_click(event):
    print("ボタンがクリックされました")

root = tk.Tk()
button = tk.Button(root, text="クリック")
button.bind("<Button-1>", on_button_click)
button.pack()
root.mainloop()

キーボードイベント

キーボード入力が行われた時に発生するイベントです。

import tkinter as tk

def on_key_press(event):
    print(f"キーが押されました: {event.keysym}")

root = tk.Tk()
root.bind("<KeyPress>", on_key_press)
root.mainloop()

マウスイベント

マウスがクリックされた時のイベントです。ボタンウィジェット以外のウィジェットでマウス操作された時の処理を書きたい時に使います。

import tkinter as tk

def on_mouse_click(event):
    print(f"マウスがクリックされました: ({event.x}, {event.y})")

root = tk.Tk()
root.bind("<Button-1>", on_mouse_click)
root.mainloop()

レイアウト管理

ウィジェットを画面のどの位置に表示するかを pack()、grid()、place() で指定します。ウィジェット1つ1つに対して、これらいづれかを呼ばないと、いつまで経っても画面に表示されません。

パック(pack)マネージャー

pack()は、ウィジェットを順番に配置します。

import tkinter as tk

root = tk.Tk()
tk.Label(root, text="上").pack(side=tk.TOP)
tk.Label(root, text="下").pack(side=tk.BOTTOM)
tk.Label(root, text="左").pack(side=tk.LEFT)
tk.Label(root, text="右").pack(side=tk.RIGHT)
root.mainloop()

グリッド(grid)マネージャー

grid()は、ウィジェットをグリッド状に配置します。グリッドの縦と横は事前に決めておく必要はなく、rowと columnで指定した値によって随時画面が分割されていくイメージです。

import tkinter as tk

root = tk.Tk()
tk.Label(root, text="行 0, 列 0").grid(row=0, column=0)
tk.Label(root, text="行 0, 列 1").grid(row=0, column=1)
tk.Label(root, text="行 1, 列 0").grid(row=1, column=0)
tk.Label(root, text="行 1, 列 1").grid(row=1, column=1)
root.mainloop()

プレース(place)マネージャー

place()は、ウィジェットを絶対座標で配置します。

import tkinter as tk

root = tk.Tk()
tk.Label(root, text="絶対位置").place(x=50, y=50)
root.mainloop()

レイアウトのコツと注意点

  • ウィジェットの配置順序に注意
  • グリッドを使うときは、各ウィジェットの位置を明確に指定
  • プレースは細かい位置調整に有効ですが、動的なレイアウト変更には不向き

Tkinterでのユーザーコントロールの作り方

ユーザーコントロール(カスタムウィジェット)は、複数の既存ウィジェットを組み合わせて新しいウィジェットを作成する方法です。
書き方は決まっていて、以下のテンプレート(青文字はそのまま)でユーザーコントロールを定義します。

class クラス名(tk.Frame):

def __init__(self, parent, label_text, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
~ここに必要なウィジェットを配置~
~ウィジェットごとに配置メソッド(pack、gridなど)を記述~


def メソッド名(self,引数1,・・・):
~ここに処理を記述~

この例では、InputPanelというカスタムウィジェットを作成しました。このウィジェットは、ラベルとエントリーフィールドを含みます。メインウィンドウには、2つのInputPanelウィジェットが配置され、それぞれ「名前」と「年齢」を入力するフィールドとして使用されています。

import tkinter as tk
from tkinter import ttk

class InputPanel(tk.Frame):
    """
    InputPanelは、ラベルとエントリー(入力フィールド)を含むカスタムウィジェットです。
    """
    def __init__(self, parent, label_text, *args, **kwargs):
        """
        初期化メソッド。InputPanelウィジェットを初期化します。

        Args:
            parent: 親ウィジェット。
            label_text: ラベルに表示するテキスト。
            *args: 可変長引数。
            **kwargs: キーワード引数。
        """
        super().__init__(parent, *args, **kwargs)
        
        # ラベルを作成して配置
        self.label = tk.Label(self, text=label_text)
        self.label.pack(side=tk.LEFT, padx=5, pady=5)
        
        # エントリーフィールドを作成して配置
        self.entry = tk.Entry(self)
        self.entry.pack(side=tk.LEFT, padx=5, pady=5)
    
    def get_value(self):
        """
        エントリーフィールドの現在の値を取得します。

        Returns:
            str: エントリーフィールドのテキスト。
        """
        return self.entry.get()

    def set_value(self, value):
        """
        エントリーフィールドに値を設定します。

        Args:
            value (str): エントリーフィールドに設定するテキスト。
        """
        self.entry.delete(0, tk.END)
        self.entry.insert(0, value)

# メインウィンドウの作成
root = tk.Tk()
root.title("カスタムウィジェットの例")

# カスタム入力パネルの作成と配置
input_panel1 = InputPanel(root, "名前:")
input_panel1.pack(padx=10, pady=10)

input_panel2 = InputPanel(root, "年齢:")
input_panel2.pack(padx=10, pady=10)

def show_values():
    """
    入力パネルの値を取得してコンソールに表示します。
    """
    print(f"名前: {input_panel1.get_value()}")
    print(f"年齢: {input_panel2.get_value()}")

# ボタンの作成と配置
button = tk.Button(root, text="表示", command=show_values)
button.pack(padx=10, pady=10)

# メインループの開始
root.mainloop()

本ブログのオリジナルユーザーコントロール

本ブログで紹介する自作ツールでは、次のユーザーコントロールを使っています。ファイル選択ダイアログとフォルダ選択ダイアログは、テキスト部分にファイルやフォルダをドラッグ&ドロップできます。
ここで紹介したユーザーコントロールは、今後必要に応じて随時更新していく予定です。

# pip install tkinterdnd2

import pandas as pd
import tkinter as tk
from tkinter import filedialog
import tkinter.ttk as ttk
from tkinterdnd2 import TkinterDnD, DND_FILES,DND_ALL
from PIL import Image, ImageTk
import os

class ComboBoxControl(tk.Frame):
    """
    コンボボックスコントロール
    """
    def __init__(self, master=None, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.combobox = ttk.Combobox(self, state="readonly")  # テキストボックス部分を読み取り専用に設定
        self.combobox.pack()
        
    def get(self):
        """
        テキストボックスの値を取得するメソッド。

        Returns:
            str: テキストボックスの値
        """
        return self.combobox.get()
    
    def set_items(self, text,items):
        """
        リストボックスにアイテムを設定するメソッド。

        Args:
            text: テキスト部にセットする文字列
            items: アイテムのリスト

        Returns:
            None
        """
        self.combobox.delete(0, tk.END)
        self.combobox.set(text)
        self.combobox["values"] = items
 
        
class ListBoxControl(tk.Frame):
    """
    リストボックスコントロール
    """
    def __init__(self, master=None, *args, **kwargs):
        super().__init__(master, *args, **kwargs)

        self.listbox = tk.Listbox(self)  # コンストラクタ内でlistbox属性を初期化
        self.listbox.pack()

    def get(self):
        """
        テキストボックスの値を取得するメソッド。

        Returns:
            str: テキストボックスの値
        """
        selected_index = self.listbox.curselection()
        if selected_index == ():
            return None
        else:
            return self.listbox.get(selected_index[0])
            
    def set_items(self, items):
        """
        リストボックスにアイテムを設定するメソッド。

        Args:
            items: アイテムのリスト

        Returns:
            None
        """
        self.listbox.delete(0, tk.END)  # 既存のアイテムを削除
        for item in items:
            self.listbox.insert(tk.END, item)  # 新しいアイテムを追加


class CenterWindow:
    def __init__(self,root,width=None,height=None):

        # 画面サイズの下限を設定
        root.minsize(width=width, height=height)

        # 画面サイズを取得
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()

        # ウィンドウのサイズと位置を計算
        x = (screen_width - width) // 2  # ウィンドウを画面中央に配置するためのX座標
        y = (screen_height - height) // 2  # ウィンドウを画面中央に配置するためのY座標

        # ウィンドウのサイズと位置を設定
        root.geometry(f"{width}x{height}+{x}+{y}")


class FileSelectionControl(tk.Frame):
    """
    ファイル名選択ダイアログ
    """
    def __init__(self, master=None, *args, **kwargs):
        """
        FileSelectionControlクラスのコンストラクタ。

        Args:
            master: 親ウィジェット
            *args: 位置引数
            **kwargs: キーワード引数
        """
        super().__init__(master, *args, **kwargs)
        
        # 先頭のテキスト文字
        self.text = "フォルダ名"
        
        # ファイル拡張子フィルター
        self.filetypes  = [("All FIles", "*.*")]
        
        # ラベルを生成
        self.label = tk.Label(self, text=self.text)
        self.label.grid(row=0, column=0, padx=10, pady=10)

        # テキストボックスを生成
        self.entry = tk.Entry(self)
        # sticky="ew" でテキストボックスが左右に広がるようにする
        self.entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

        # アイコンのイメージを取得
        icon_image = Image.open("./images/folder_Open_16xLG.png")
        icon = ImageTk.PhotoImage(icon_image)

        # ファイル名選択ボタンを生成
        self.button = tk.Button(self, text="ファイルを選択", image=icon, compound="left", command=self.open_file_dialog)
        self.button.image = icon
        self.button.grid(row=0, column=2, padx=10, pady=10)

        # ドロップを許可するフォーマットを設定
        self.entry.drop_target_register(DND_FILES)
        self.entry.dnd_bind('<<Drop>>', self.on_drop)

        # グリッド列の重みを設定してテキストボックスがウィンドウのサイズ変更に追従するようにする
        self.grid_columnconfigure(1, weight=1)

    def get(self):
        """
        テキストボックスの値を取得するメソッド。

        Returns:
            str: テキストボックスの値
        """
        return self.entry.get()
    
    def open_file_dialog(self):
        """
        ファイル選択ダイアログを開くメソッド。
        テキストボックスに入力されたパスを取得し、選択されたファイルパスを表示する。

        Returns:
            None
        """
        # テキストボックスに入力されたパスを取得
        path = self.entry.get()
        file_path = None
        if path is None or path == '':
            # パスが未入力の場合、ファイル選択ダイアログを開く
            file_path = filedialog.askopenfilename(filetypes=self.filetypes)
        elif os.path.isfile(path):
            # テキストボックスのパスが有効なファイルの場合、そのディレクトリを初期ディレクトリとしてファイル選択ダイアログを開く
            file_path = filedialog.askopenfilename(initialdir=os.path.dirname(path),filetypes=self.filetypes)

        if file_path:
            # 選択されたファイルパスをテキストボックスに表示
            self.entry.delete(0, tk.END)
            self.entry.insert(0, file_path)

    def on_drop(self, event):
        """
        ドロップされたファイルのパスを取得し、テキストボックスに表示するメソッド。

        Args:
            event: ドロップイベント

        Returns:
            None
        """
        # ドロップされたファイルのパスを取得
        file_path = event.data
        if os.path.isfile(file_path) :
            # テキストボックスにドロップされたファイルパスを表示
            self.entry.delete(0, tk.END)
            self.entry.insert(0, file_path)
 

class FolderSelectionControl(tk.Frame):
    """
    フォルダ名選択ダイアログ
    """
    def __init__(self, master=None, *args, **kwargs):
        """
        FolderSelectionControl

        Args:
            master: 親ウィジェット
            *args: 位置引数
            **kwargs: キーワード引数
        """
        super().__init__(master, *args, **kwargs)
        
        # 先頭のテキスト文字
        self.text = "フォルダ名"
        
        # ラベルを生成
        self.label = tk.Label(self, text=self.text)
        self.label.grid(row=0, column=0, padx=10, pady=10)

        # テキストボックスを生成
        self.entry = tk.Entry(self)
        # sticky="ew" でテキストボックスが左右に広がるようにする
        self.entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

        # アイコンのイメージを取得
        icon_image = Image.open("./images/folder_Open_16xLG.png")
        icon = ImageTk.PhotoImage(icon_image)

        # フォルダ名選択ボタンを生成
        self.button = tk.Button(self, text="フォルダを選択", image=icon, compound="left", command=self.open_folder_dialog)
        self.button.image = icon
        self.button.grid(row=0, column=2, padx=10, pady=10)

        # ドロップを許可するフォーマットを設定
        self.entry.drop_target_register(DND_ALL)
        self.entry.dnd_bind('<<Drop>>', self.on_drop)

        # グリッド列の重みを設定してテキストボックスがウィンドウのサイズ変更に追従するようにする
        self.grid_columnconfigure(1, weight=1)

    def get(self):
        """
        テキストボックスの値を取得するメソッド。

        Returns:
            str: テキストボックスの値
        """
        return self.entry.get()
    
    def open_folder_dialog(self):
        """
        フォルダ選択ダイアログを開くメソッド。
        テキストボックスに入力されたパスを取得し、選択されたファイルパスを表示する。

        Returns:
            None
        """
        # テキストボックスに入力されたパスを取得
        path = self.entry.get()
        folder_path = None
        if path is None or path == '':
            # パスが未入力の場合、ファイル選択ダイアログを開く
            folder_path = filedialog.askdirectory()
        elif os.path.isdir(path):
            # テキストボックスのパスが有効なファイルの場合、そのディレクトリを初期ディレクトリとしてファイル選択ダイアログを開く
            folder_path = filedialog.askdirectory(initialdir=os.path.dirname(path))

        if folder_path:
            # 選択されたファイルパスをテキストボックスに表示
            self.entry.delete(0, tk.END)
            self.entry.insert(0, folder_path)

    def on_drop(self, event):
        """
        ドロップされたファイルのパスを取得し、テキストボックスに表示するメソッド。

        Args:
            event: ドロップイベント

        Returns:
            None
        """
        # ドロップされたファイルのパスを取得
        folder_path = event.data
        # ファイルがドロップされたらフォルダ名を取得
        if os.path.isfile(folder_path):
            folder_path = os.path.dirname(folder_path)
        # folder_pathがフォルダ名か否かをチェック   
        if os.path.isdir(folder_path) :
            # テキストボックスにドロップされたファイルパスを表示
            self.entry.delete(0, tk.END)
            self.entry.insert(0, folder_path)


class SaveFileDialogControl(tk.Frame):
    """
    ファイル保存用のファイル名選択ダイアログ
    """
    def __init__(self, master=None, *args, **kwargs):
        """
        SaveFileDialogControlクラスのコンストラクタ。

        Args:
            master: 親ウィジェット
            *args: 位置引数
            **kwargs: キーワード引数
        """
        super().__init__(master, *args, **kwargs)
        
        # 先頭のテキスト文字
        self.text = "ファイル名"
        
        # ラベルを生成
        self.label = tk.Label(self, text=self.text)
        self.label.grid(row=0, column=0, padx=10, pady=10)

        # テキストボックスを生成
        self.entry = tk.Entry(self)
        # sticky="ew" でテキストボックスが左右に広がるようにする
        self.entry.grid(row=0, column=1, padx=10, pady=10, sticky="ew")

        # アイコンのイメージを取得
        icon_image = Image.open("./images/save_16xLG.png")
        icon = ImageTk.PhotoImage(icon_image)

        # ファイル保存ボタンを生成
        self.button = tk.Button(self, text="ファイルを保存", image=icon, compound="left", command=self.save_file_dialog)
        self.button.image = icon
        self.button.grid(row=0, column=2, padx=10, pady=10)

        # グリッド列の重みを設定してテキストボックスがウィンドウのサイズ変更に追従するようにする
        self.grid_columnconfigure(1, weight=1)

    def get(self):
        """
        テキストボックスの値を取得するメソッド。

        Returns:
            str: テキストボックスの値
        """
        return self.entry.get()
    
    def save_file_dialog(self):
        """
        ファイル保存ダイアログを開くメソッド。
        テキストボックスに入力されたファイル名を取得し、保存先ファイルパスを表示する。

        Returns:
            None
        """
        # テキストボックスに入力されたファイル名を取得
        file_name = self.entry.get()
            # ファイル保存ダイアログを開く
        file_path = filedialog.asksaveasfilename(defaultextension=".txt", initialfile=file_name)
        if file_path:
            # 選択された保存先ファイルパスをテキストボックスに表示
            self.entry.delete(0, tk.END)
            self.entry.insert(0, file_path)


class DataFrameViewer(tk.Frame):
    def __init__(self, master=None, *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.grid_columnconfigure(0, weight=1)
        self.grid_rowconfigure(0, weight=1)

        # Treeviewを作成
        self.tree = ttk.Treeview(self)
        self.tree["show"] = "headings"

        # ドロップを許可するフォーマットを設定
        self.tree.drop_target_register(DND_FILES)
        self.tree.dnd_bind('<<Drop>>', self.on_drop)

        # Treeviewを配置
        self.tree.grid(row=0, column=0, sticky="nsew")

    def load_dataframe(self, df):
        # 既存のデータを削除
        self.tree.delete(*self.tree.get_children())

        # カラムの設定
        self.tree["columns"] = tuple(df.columns)
        for col in df.columns:
            self.tree.heading(col, text=col)
            self.tree.column(col, width=100, minwidth=100, stretch=tk.NO)

        # データを挿入
        for i in range(len(df)):
            self.tree.insert("", "end", values=tuple(df.iloc[i]))

    def on_drop(self, event):
        # ドロップされたファイルのパスを取得
        file_path = event.data
        if isinstance(file_path, list):
            file_path = file_path[0]  # ドロップされたファイルのリストから最初のファイルを取得

        # CSVファイルをDataFrameに変換
        try:
            df = pd.read_csv(file_path)
            self.load_dataframe(df)
        except pd.errors.EmptyDataError:
            print("CSVファイルが空です。")

下記はオリジナルユーザーコントロールを張り付けたデモ画面です。

画面の初期状態

ウィンドウサイズに合わせてユーザーコントロールの幅が追従

使い方は下記のソースを参考にしてください。

# pip install tkinterdnd2
import pandas as pd
import tkinter as tk
from tkinterdnd2 import TkinterDnD
from tkintercontrols import FileSelectionControl
from tkintercontrols import FolderSelectionControl
from tkintercontrols import SaveFileDialogControl
from tkintercontrols import DataFrameViewer
from tkintercontrols import ListBoxControl
from tkintercontrols import ComboBoxControl
# TkinterDnDを使用してドラッグ&ドロップ対応のTkインスタンスを作成
root = TkinterDnD.Tk()
root.title("ファイル選択ダイアログのサンプル")
# ファイル選択コントロールを作成してウィンドウに配置
file_selection = FileSelectionControl(root)
# fill='x' と expand=True でフレームがウィンドウのサイズ変更に応じて広がるようにする
file_selection.filetypes = [("csv","*.csv;*.tsv"),("All Files","*.*")]
file_selection.pack(padx=10, pady=10, fill='x', expand=True)

folder_selection = FolderSelectionControl(root)
# fill='x' と expand=True でフレームがウィンドウのサイズ変更に応じて広がるようにする
folder_selection.pack(padx=10, pady=10, fill='x', expand=True)

folder_selection = SaveFileDialogControl(root)
# fill='x' と expand=True でフレームがウィンドウのサイズ変更に応じて広がるようにする
folder_selection.pack(padx=10, pady=10, fill='x', expand=True)

lbox = ListBoxControl(root)
lbox.set_items(["aaa","bbb","ccc"])
lbox.pack()

cbox = ComboBoxControl(root)
cbox.set_items("aaa",["aaa","bbb","ccc"])
cbox.pack()


viewer = DataFrameViewer(root)
viewer.pack(padx=10, pady=10,fill="both", expand=True)


sample_df = pd.DataFrame({
        "Name": ["John Doe", "Anna Smith", "Peter Brown"],
        "Age": [25, 30, 28],
        "Country": ["USA", "UK", "Canada"]
    })
viewer.load_dataframe(sample_df)

def func():
    print(lbox.get())
    print(cbox.get())
    print(file_selection.get())
    
btn = tk.Button(root,text="実行",command=func)
btn.pack()

# メインループを開始
root.mainloop()

まとめ

TkinterはPythonでGUI(グラフィカルユーザインタフェース)アプリケーションを作成するための標準ライブラリであり、Pythonで作った画面にウィンドウ、ボタン、テキストボックス、ラベルなどのGUI要素を簡単に追加することができます。

本記事では、Tkinterで用意されているウィジェットについて、具体的なサンプルソースを紹介しながら解説しました。

また、画面レイアウトに関するメソッドや、イベントの取り扱い、複数のウィジェットを組み合わせたユーザーコントロールの作り方についても紹介しました。

そして最後に、本ブログの自作ツールで使用している、オリジナルのユーザーコントロールのソースコードも公開しました。

Tkinterはちょっとした画面を作るには便利なライブラリなので、本ブログで紹介する自作ツールには最適です。見栄えは今どきの画面デザインからすると少し型落ち感は拭えませんが、機能的としては充実していますので、是非これを機にTkinterを使ってみてください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次