tklib.tkgui.tktkinter のソースコード
"""
概要:
Tkinterウィジェットのラッパー関数とカスタムウィジェットを提供するモジュールです。
詳細説明:
このモジュールは、Tkinterアプリケーション開発を簡素化し、
共通のUIコンポーネント、ファイル/フォルダ選択ダイアログ、メッセージボックス、
カスタム入力ウィジェットなどを提供します。
Matplotlibの埋め込みキャンバスや、右クリックメニュー付きのEntry/Spinbox、
設定ダイアログジェネレータなど、より高度な機能も含まれています。
関連リンク:
tktkinter_usage
"""
import os
import subprocess
import sys
import glob
import re
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter
import tkinter.font
from tkinter.font import Font, nametofont
from tkinter import colorchooser
#will be implemented in tk8.6.13")
#from tkinter import fontchooser
import tkinter.ttk
from tklib.tkutils import pint, pfloat, pconv, is_file, get_file_masks, getenv, get_os
from tklib.tkutils import get_file_list, split_file_path, find_executable_path
from tklib.tkparams import tkParams
[ドキュメント]
def set_variable_from_list(app, key, var):
"""
概要:
Tkinter変数をリストから設定し、アプリケーションの変数管理に登録します。
詳細説明:
app.tkvarsにキーと変数を関連付けて保存します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param key: 変数を識別するためのキー文字列。
:type key: str
:param var: 設定するTkinter変数(StringVar, IntVar, BooleanVarなど)。
:type var: tkinter.Variable
戻り値:
:returns: 設定されたTkinter変数。
:rtype: tkinter.Variable
"""
app.tkvars.set_attr(key, var)
return var
[ドキュメント]
def execute_command(app, command, working_dir = None, is_print = False, files = []):
"""
概要:
外部コマンドを実行します。
詳細説明:
コマンド文字列を解析し、オプションの作業ディレクトリとファイル引数を適用してos.systemで実行します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param command: 実行するコマンド文字列。角括弧で囲まれた引数を含めることができます。
:type command: str
:param working_dir: (オプション) コマンドを実行する作業ディレクトリ。デフォルトはNone。
:type working_dir: str or None
:param is_print: (オプション) 実行するコマンドを標準出力に表示するかどうか。デフォルトはFalse。
:type is_print: bool
:param files: (オプション) コマンドに追加するファイルパスのリスト。デフォルトは空のリスト。
:type files: list
戻り値:
:returns: os.systemの戻り値(通常はコマンドの終了ステータス)。
:rtype: int
"""
# print("command=", command)
# print("working_dir=", working_dir)
a = command.split('[')
if len(a) >= 2:
command, arg = a[0], a[1]
a = arg.split(']')
if len(a) >= 2:
arg = a[0]
arg = f'{arg}'
else:
arg = ''
command = command.strip()
arg = arg.strip()
if working_dir is not None:
arg = arg.replace('%(dir)', working_dir)
if command == 'auto':
cmd = ''
elif ' ' in command:
cmd = f'"{command}"'
else:
cmd = f'{command}'
cmd = f'{cmd} {arg}'
if arg == '':
for s in files:
if ' ' in s:
cmd += f' "{s}"'
else:
cmd += f' {s}'
if is_print:
print(f"cmd: [{cmd}]")
return os.system(cmd)
[ドキュメント]
def folder_button_click(app, entry_variable, ini_dir = '.', title = None, mustexist = True,
entry_type = 'entry', entry_filemask = '*;*.*'):
"""
概要:
フォルダ選択ダイアログを表示し、選択されたパスをTkinter変数に設定します。
詳細説明:
tkinter.filedialog.askdirectoryを使用してフォルダを選択させ、結果をentry_variableに格納します。
entry_typeが'combobox'の場合、選択されたパス内のファイルリストでコンボボックスの値を更新します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param entry_variable: 選択されたフォルダパスを格納するTkinter変数、またはComboboxウィジェット。
:type entry_variable: tkinter.Variable or tkinter.ttk.Combobox
:param ini_dir: (オプション) ダイアログの初期ディレクトリ。デフォルトは'.'。
:type ini_dir: str
:param title: (オプション) ダイアログのタイトル。デフォルトはNone。
:type title: str or None
:param mustexist: (オプション) 選択されたディレクトリが存在しなければならないか。デフォルトはTrue。
:type mustexist: bool
:param entry_type: (オプション) entry_variableのタイプ ('entry'または'combobox')。'combobox'の場合、ファイルリストで更新されます。
:type entry_type: str
:param entry_filemask: (オプション) entry_typeが'combobox'の場合にファイルリスト取得に使用するファイルマスク。
:type entry_filemask: str
"""
seldir = tkinter.filedialog.askdirectory(initialdir = ini_dir, title = title, mustexist = mustexist)
if seldir is not None and seldir != '':
entry_variable.set(seldir)
entry_variable.prev_val = seldir # 'selpath'を'seldir'に修正
if entry_type == 'combobox':
dirs, files = get_file_list(seldir, entry_filemask) # 'selpath'を'seldir'に修正
for f in files:
dirs.append(f)
entry_variable['values'] = dirs
[ドキュメント]
def path_button_click(app, entry_variable, ini_dir = '.', title = None, file_type = [("All", "*.*")],
entry_type = 'entry', entry_filemask = '*;*.*', check_exist = True):
"""
概要:
ファイル選択または保存ダイアログを表示し、選択されたパスをTkinter変数に設定します。
詳細説明:
tkinter.filedialog.askopenfilenameまたはasksaveasfilenameを使用してファイルを選択させ、
結果をentry_variableに格納します。entry_typeが'combobox'の場合、
選択されたパス内のファイルリストでコンボボックスの値を更新します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param entry_variable: 選択されたファイルパスを格納するTkinter変数、またはComboboxウィジェット。
:type entry_variable: tkinter.Variable or tkinter.ttk.Combobox
:param ini_dir: (オプション) ダイアログの初期ディレクトリ。デフォルトは'.'。
:type ini_dir: str
:param title: (オプション) ダイアログのタイトル。デフォルトはNone。
:type title: str or None
:param file_type: (オプション) ダイアログに表示するファイルタイプのリスト。例: [("Text files", "*.txt")]。
:type file_type: list
:param entry_type: (オプション) entry_variableのタイプ ('entry'または'combobox')。'combobox'の場合、ファイルリストで更新されます。
:type entry_type: str
:param entry_filemask: (オプション) entry_typeが'combobox'の場合にファイルリスト取得に使用するファイルマスク。
:type entry_filemask: str
:param check_exist: (オプション) ファイルが存在するかどうかを確認するか(askopenfilenameまたはasksaveasfilenameのどちらを使用するか)。デフォルトはTrue。
:type check_exist: bool
"""
if check_exist:
selpath = tkinter.filedialog.askopenfilename(filetypes = file_type, initialdir = ini_dir, title = title)
else:
selpath = tkinter.filedialog.asksaveasfilename(filetypes = file_type, initialdir = ini_dir, title = title)
if selpath is not None and selpath != '':
entry_variable.set(selpath)
entry_variable.prev_val = selpath
if entry_type == 'combobox':
dirs, files = get_file_list(selpath, entry_filemask)
for f in files:
dirs.append(f)
entry_variable['values'] = dirs
[ドキュメント]
def edit_button_click(app, entry_variable, editor_path = None):
"""
概要:
指定されたエディタでファイルを開きます。
詳細説明:
entry_variableからファイルパスを取得し、editor_pathで指定されたエディタで
os.systemを使ってファイルを開きます。editor_pathがNoneの場合、app.get_editor()から取得します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param entry_variable: 編集するファイルパスを含むTkinter変数。
:type entry_variable: tkinter.Variable
:param editor_path: (オプション) 使用するエディタのパス。Noneの場合、app.get_editor()を使用。
:type editor_path: str or None
"""
if editor_path is editor_path: # この条件は常にTrueになるため、実質的に常にapp.get_editor()が呼び出される可能性がある
editor_path = app.get_editor()
path = entry_variable.get()
if ' ' in editor_path:
editor_path = f'"{editor_path}"'
if ' ' in path:
path = f'"{path}"'
cmd = editor_path + ' ' + path
print(f"* Execute [{cmd}]")
os.system(cmd)
# subprocess.call(cmd)
[ドキュメント]
def shell_button_click(app, cmd = None):
"""
概要:
コマンドプロンプトまたはシェルを開きます。
詳細説明:
ComSpec環境変数を参照してシェルコマンド(通常はcmd.exe)を取得し、
os.systemで実行します。cmdが指定されない場合、新しいシェルウィンドウを開きます。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param cmd: (オプション) 実行する特定のシェルコマンド。Noneの場合、デフォルトのシェルを開きます。
:type cmd: str or None
"""
comspec = getenv('ComSpec', 'cmd.exe')
if cmd is None:
cmd = f'start {comspec}'
print(f"* Execute [{cmd}]")
os.system(cmd)
[ドキュメント]
def color_dialog(app = None, parent = None, title = None, color = None):
"""
概要:
色選択ダイアログを表示します。
詳細説明:
tkinter.colorchooser.askcolorを呼び出し、ユーザーが色を選択できるようにします。
引数:
:param app: (オプション) アプリケーションのインスタンス。主にparentの指定に使用されます。
:type app: object or None
:param parent: (オプション) ダイアログの親ウィジェット。
:type parent: tkinter.Widget or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param color: (オプション) ダイアログの初期色。
:type color: str or tuple or None
戻り値:
:returns: ((R, G, B), '#RRGGBB')形式の選択された色情報、または選択がキャンセルされた場合は(None, None)。
:rtype: tuple
"""
return colorchooser.askcolor(parent = parent, title = title, color = color)
# return colorchooser.askcolor(parent = app.root_window, title = title, color = color) # この行はコメントアウトされており、使用されない
[ドキュメント]
def font_dialog(app = None, parent = None, title = None, color = None):
"""
概要:
フォント選択ダイアログを表示します。
詳細説明:
tk fontchooserコマンドを呼び出し、ユーザーがフォントを選択できるようにします。
選択されたフォントはtkParamsインスタンスを通じて返されます。
引数:
:param app: (オプション) アプリケーションのインスタンス。主にroot_windowの指定に使用されます。
:type app: object or None
:param parent: (オプション) ダイアログの親ウィジェット。
:type parent: tkinter.Widget or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param color: (オプション) 現在使用されていません。
:type color: any
戻り値:
:returns: 選択されたフォントオブジェクト。
:rtype: tkinter.font.Font
"""
# return fontchooser.askcolor(parent = parent, title = title, color = color)
sel = tkParams()
sel.font = None
def font_changed(sel, sel_font):
sel.font = sel_font
app.root_window.tk.call('tk', 'fontchooser', 'configure', '-font', 'helvetica 24',
'-command', app.root_window.register(lambda sel_font: font_changed(sel, sel_font)))
app.root_window.tk.call('tk', 'fontchooser', 'show')
return sel.font
[ドキュメント]
def dialog_askfloat(app, title = 'Input float value', message = 'Input float value',
initialvalue = None, minvalue = None, maxvalue = None):
"""
概要:
浮動小数点数入力を求めるダイアログを表示します。
詳細説明:
tkinter.simpledialog.askfloatを使用して、タイトル、メッセージ、
初期値、最小値、最大値を含む入力ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) ダイアログのタイトル。
:type title: str
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str
:param initialvalue: (オプション) 入力フィールドの初期値。
:type initialvalue: float or None
:param minvalue: (オプション) 許容される最小値。
:type minvalue: float or None
:param maxvalue: (オプション) 許容される最大値。
:type maxvalue: float or None
戻り値:
:returns: ユーザーが入力した浮動小数点数、またはキャンセルされた場合はNone。
:rtype: float or None
"""
return tkinter.simpledialog.askfloat(app.p(title), app.p(message),
initialvalue = initialvalue, minvalue = minvalue, maxvalue = maxvalue, parent = app.root_window)
[ドキュメント]
def askinteger(app, title = 'Input float value', message = 'Input float value',
initialvalue = None, minvalue = None, maxvalue = None):
"""
概要:
整数入力を求めるダイアログを表示します。
詳細説明:
tkinter.simpledialog.askintegerを使用して、タイトル、メッセージ、
初期値、最小値、最大値を含む入力ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) ダイアログのタイトル。
:type title: str
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str
:param initialvalue: (オプション) 入力フィールドの初期値。
:type initialvalue: int or None
:param minvalue: (オプション) 許容される最小値。
:type minvalue: int or None
:param maxvalue: (オプション) 許容される最大値。
:type maxvalue: int or None
戻り値:
:returns: ユーザーが入力した整数、またはキャンセルされた場合はNone。
:rtype: int or None
"""
return tkinter.simpledialog.askinteger(app.p(title), app.p(message),
initialvalue = initialvalue, minvalue = minvalue, maxvalue = maxvalue, parent = app.root_window)
[ドキュメント]
def askstring(app, title = 'Input text', message = 'Input float value',
initialvalue = None):
"""
概要:
文字列入力を求めるダイアログを表示します。
詳細説明:
tkinter.simpledialog.askstringを使用して、タイトル、メッセージ、
初期値を含む入力ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) ダイアログのタイトル。
:type title: str
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str
:param initialvalue: (オプション) 入力フィールドの初期値。
:type initialvalue: str or None
戻り値:
:returns: ユーザーが入力した文字列、またはキャンセルされた場合はNone。
:rtype: str or None
"""
return tkinter.simpledialog.askstring(app.p(title), app.p(message), initialvalue = initialvalue, parent = app.root_window)
[ドキュメント]
def dialog_showinfo(app, title = 'Information', message = 'no message'):
"""
概要:
情報メッセージボックスを表示します。
詳細説明:
tkinter.messagebox.showinfoを使用して、タイトルとメッセージを含む情報ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
"""
tkinter.messagebox.showinfo(app.p(title), message, parent = app.root_window)
[ドキュメント]
def dialog_showwarning(app, title = 'Warning', message = 'no message'):
"""
概要:
警告メッセージボックスを表示します。
詳細説明:
tkinter.messagebox.showwarningを使用して、タイトルとメッセージを含む警告ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
"""
tkinter.messagebox.showwarning(app.p(title), app.p(message), parent = app.root_window)
[ドキュメント]
def dialog_showerror(app, title = 'Error', message = 'no message'):
"""
概要:
エラーメッセージボックスを表示します。
詳細説明:
tkinter.messagebox.showerrorを使用して、タイトルとメッセージを含むエラーダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
"""
tkinter.messagebox.showerror(app.p(title), app.p(message), parent = app.root_window)
[ドキュメント]
def dialog_retrycancel(app, title = 'Retry?', message = 'Do you want to retry?'):
"""
概要:
「再試行」または「キャンセル」を選択するダイアログを表示します。
詳細説明:
tkinter.messagebox.askretrycancelを使用して、タイトルとメッセージを含む確認ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
戻り値:
:returns: ユーザーが「再試行」を選択した場合はTrue、「キャンセル」を選択した場合はFalse。
:rtype: bool
"""
return tkinter.messagebox.askretrycancel(app.p(title), app.p(message), parent = app.root_window)
[ドキュメント]
def dialog_yesno(app, title = 'Quit', message = 'Do you really quit the program?'):
"""
概要:
「はい」または「いいえ」を選択するダイアログを表示します。
詳細説明:
tkinter.messagebox.askyesnoを使用して、タイトルとメッセージを含む確認ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
戻り値:
:returns: ユーザーが「はい」を選択した場合はTrue、「いいえ」を選択した場合はFalse。
:rtype: bool
"""
# root = tkinter.Tk()
root = app.root_window
# print("root=", root)
return tkinter.messagebox.askyesno(app.p(title), app.p(message), parent = root)
[ドキュメント]
def dialog_okcancel(app, title = 'Quit', message = 'Do you really quit the program?'):
"""
概要:
「OK」または「キャンセル」を選択するダイアログを表示します。
詳細説明:
tkinter.messagebox.askokcancelを使用して、タイトルとメッセージを含む確認ダイアログを表示します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param title: (オプション) メッセージボックスのタイトル。
:type title: str
:param message: (オプション) メッセージボックスに表示するメッセージ。
:type message: str
戻り値:
:returns: ユーザーが「OK」を選択した場合はTrue、「キャンセル」を選択した場合はFalse。
:rtype: bool
"""
return tkinter.messagebox.askokcancel(app.p(title), app.p(message), parent = app.root_window)
[ドキュメント]
def Menu(app, parent = None, **kwargs):
"""
概要:
Tkinterのメニューウィジェットを作成します。
詳細説明:
tkinter.Menuインスタンスを生成し、親ウィジェットと追加のオプションを受け取ります。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) メニューの親ウィジェット。
:type parent: tkinter.Widget or None
:param **kwargs: tkinter.Menuに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたメニューウィジェット。
:rtype: tkinter.Menu
"""
return tkinter.Menu(parent, **kwargs)
[ドキュメント]
def Frame(app, parent = None, **kwargs):
"""
概要:
Tkinterのフレームウィジェットを作成します。
詳細説明:
tkinter.Frameインスタンスを生成し、親ウィジェットと追加のオプションを受け取ります。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) フレームの親ウィジェット。
:type parent: tkinter.Widget or None
:param **kwargs: tkinter.Frameに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたフレームウィジェット。
:rtype: tkinter.Frame
"""
return tkinter.Frame(parent, **kwargs)
[ドキュメント]
def CheckButton(app, parent = None, variable = None, defvalue = False, command = None,
onvalue = True, offvalue = False, **kwargs):
"""
概要:
Tkinterのチェックボタンウィジェットを作成します。
詳細説明:
tkinter.Checkbuttonインスタンスを生成し、必要に応じてtkinter.BooleanVarを自動的に作成して関連付けます。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) チェックボタンの親ウィジェット。
:type parent: tkinter.Widget or None
:param variable: (オプション) チェックボタンの状態を管理するTkinter変数(BooleanVarなど)。指定されない場合、自動生成されます。
:type variable: tkinter.Variable or None
:param defvalue: (オプション) variableが自動生成される場合の初期値。デフォルトはFalse。
:type defvalue: bool
:param command: (オプション) チェックボタンがクリックされたときに実行されるコールバック関数。
:type command: callable or None
:param onvalue: (オプション) チェックされたときにvariableに設定される値。デフォルトはTrue。
:type onvalue: any
:param offvalue: (オプション) チェックが外されたときにvariableに設定される値。デフォルトはFalse。
:type offvalue: any
:param **kwargs: tkinter.Checkbuttonに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたチェックボタンウィジェット。
:rtype: tkinter.Checkbutton
"""
if variable is None:
variable = tkinter.BooleanVar(value = defvalue)
# イベントハンドラーでget()しないと正常に初期化されない
def checked_button():
variable.get()
if command is None:
command = checked_button
check = tkinter.Checkbutton(parent, variable = variable, command = command,
onvalue = onvalue, offvalue = offvalue, **kwargs)
check.variable = variable
return check
[ドキュメント]
def Entry(app, parent = None, **kwargs):
"""
概要:
Tkinterの入力フィールドウィジェットを作成します。
詳細説明:
tkinter.Entryインスタンスを生成し、親ウィジェットと追加のオプションを受け取ります。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) エントリウィジェットの親。
:type parent: tkinter.Widget or None
:param **kwargs: tkinter.Entryに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたエントリウィジェット。
:rtype: tkinter.Entry
"""
return tkinter.Entry(parent, **kwargs)
[ドキュメント]
def Combobox(app, parent = None, **kwargs):
"""
概要:
Tkinterのコンボボックスウィジェットを作成します。
詳細説明:
tkinter.ttk.Comboboxインスタンスを生成し、親ウィジェットと追加のオプションを受け取ります。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) コンボボックスの親ウィジェット。
:type parent: tkinter.Widget or None
:param **kwargs: tkinter.ttk.Comboboxに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたコンボボックスウィジェット。
:rtype: tkinter.ttk.Combobox
"""
return tkinter.ttk.Combobox(parent, **kwargs)
[ドキュメント]
def Button(app, parent = None, **kwargs):
"""
概要:
Tkinterのボタンウィジェットを作成します。
詳細説明:
tkButtonカスタムクラスのインスタンスを生成します。ボタンのテキストはapp.p()で処理されます。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) ボタンの親ウィジェット。
:type parent: tkinter.Widget or None
:param **kwargs: tkButtonに渡される追加のキーワード引数。textは国際化処理されます。
:type **kwargs: dict
戻り値:
:returns: 作成されたボタンウィジェット。
:rtype: tkButton
"""
caption = kwargs.get('text', "")
kwargs['text'] = app.p(caption)
# return tkinter.Button(parent, **kwargs)
return tkButton(parent, app, **kwargs)
[ドキュメント]
def Text(app, parent = None, **kwargs):
"""
概要:
Tkinterのテキストウィジェットを作成します。
詳細説明:
tkinter.Textインスタンスを生成し、親ウィジェットと追加のオプションを受け取ります。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) テキストウィジェットの親。
:type parent: tkinter.Widget or None
:param **kwargs: tkinter.Textに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたテキストウィジェット。
:rtype: tkinter.Text
"""
return tkinter.Text(parent, **kwargs)
[ドキュメント]
def Canvas(app, parent = None, canvas_args = {}, **kwargs):
"""
概要:
Matplotlibの図を埋め込むことができるTkinterキャンバスウィジェットを作成します。
詳細説明:
matplotlib.backends.backend_tkagg.FigureCanvasTkAggを使用してMatplotlibのFigureを
Tkinterフレームに埋め込みます。スクロールバーの有無、サイズ、イベントハンドリングを設定します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) キャンバスを配置する親ウィジェット。
:type parent: tkinter.Widget or None
:param canvas_args: (オプション) キャンバスの動作に関する辞書型引数。
use_scrollbar: (bool) スクロールバーを使用するかどうか。
figsize: (list) MatplotlibのFigureサイズ (幅, 高さ) in inches。
min_canvas_size: (list) キャンバスの最小サイズ (幅, 高さ) in pixels。
:type canvas_args: dict
:param **kwargs: その他のキーワード引数(現在使用されていません)。
:type **kwargs: dict
戻り値:
:returns: 作成されたMatplotlibキャンバスウィジェット。
:rtype: FigureCanvasTkAgg
"""
# use_scrollbar = canvas_args.get('use_scrollbar', True)
use_scrollbar = canvas_args.get('use_scrollbar', False)
figsize = canvas_args.get('figsize', [6, 6])
min_canvas_size = canvas_args.get('min_canvas_size', [3, 3])
figure = plt.Figure(figsize = figsize, dpi = 100)
# figure = plt.subplots()
app.figure = figure
frame = tkinter.Frame(parent, relief = tkinter.GROOVE, bg = '#ff0000')
canvas = FigureCanvasTkAgg(app.figure, master = frame)
canvas_widget = canvas.get_tk_widget()
app.canvas = canvas
app.canvas_widget = canvas_widget
def __configure_canvas(event):
print("resized", event)
wi = event.width # frame.winfo_reqwidth()
hi = event.height # frame.winfo_reqheight()
dpi = app.figure.get_dpi()
wf = event.width / dpi
hf = event.height / dpi
size = (max(wi, min_canvas_size[0]), max(hi, min_canvas_size[1]))
canvas_widget.config(scrollregion = (0, 0, size[0], size[1]))
# canvas_widget.config(scrollregion=(-100, -100, 800, 600))
# if interior.winfo_reqwidth() != canvas_widget.winfo_width() or \
# interior.winfo_reqheight() != canvas_widget.winfo_height():
if use_scrollbar:
print(" canvas size to:", wi, hi)
print(" fig size fixed to:", *figsize)
canvas_widget.config(
width = wi - vscrollbar.winfo_reqwidth(),
height = hi - hscrollbar.winfo_reqheight())
plt.rcParams["figure.figsize"] = figsize
else:
print(" canvas size to:", wi, hi)
print(" fig size to:", wf, hf)
canvas_widget.config(width = wi, height = hi)
plt.rcParams["figure.figsize"] = [wf, hf]
# plt.clf()
# ax.cla()
canvas.draw()
def __pickup_point(event):
print(event.x, event.y, canvas_widget.canvasx(event.x), canvas_widget.canvasy(event.y))
def __mouse_right_click_on_canvas(event):
x = canvas_widget.canvasx(event.x)
y = canvas_widget.canvasy(event.y)
popup_menu = tkinter.Menu(canvas_widget, tearoff = 0)
popup_menu.add_command(label = 'new')
popup_menu.add_separator()
popup_menu.add_command(label = 'update')
popup_menu.tk_popup(event.x_root,event.y_root,0)
canvas_widget.bind('<ButtonPress-1>', __pickup_point)
canvas_widget.bind('<Button-3>', __mouse_right_click_on_canvas)
frame.bind('<Configure>', __configure_canvas)
if use_scrollbar:
vscrollbar = tkinter.ttk.Scrollbar(frame, orient=tkinter.VERTICAL, command = canvas_widget.xview)
hscrollbar = tkinter.ttk.Scrollbar(frame, orient=tkinter.HORIZONTAL, command = canvas_widget.yview)
vscrollbar.grid(row = 0, column = 1, stick = tkinter.N + tkinter.S)
hscrollbar.grid(row = 1, column = 0, stick = tkinter.W + tkinter.E)
canvas_widget.grid(row = 0, column = 0, stick = tkinter.N + tkinter.S + tkinter.E + tkinter.W)#, expand = 1)
vscrollbar.config(command = canvas_widget.yview)
hscrollbar.config(command = canvas_widget.xview)
# canvas_widget['xscrollcommand'] = canvas_h_scroll.set
# canvas_widget['yscrollcommand'] = canvas_v_scroll.set
canvas_widget.xview_moveto(0)
canvas_widget.yview_moveto(0)
canvas_widget.config(scrollregion=(0, 0, min_canvas_size[0], min_canvas_size[1]))
# canvas_widget.config(scrollregion=(-100, -100, 800, 800))
else:
canvas_widget.pack()
canvas._tkcanvas.pack()
frame.pack(fill = tkinter.BOTH, expand = True)
return canvas
[ドキュメント]
class tkScrolledListbox(tkinter.Listbox):
"""
概要:
スクロールバー付きのTkinterリストボックスウィジェットです。
詳細説明:
tkinter.Listboxとtkinter.Scrollbarを組み合わせて、
リストの内容が多い場合でもスクロールして表示できるようにします。
"""
def __init__(self, master, **key):
"""
概要:
tkScrolledListboxウィジェットを初期化します。
引数:
:param master: リストボックスを配置する親ウィジェット。
:type master: tkinter.Widget
:param **key: tkinter.Listboxに渡される追加のキーワード引数。
:type **key: dict
"""
self.frame = tkinter.Frame(master)
self.yscroll = tkinter.Scrollbar(self.frame, orient = tkinter.VERTICAL)
self.yscroll.pack(side = tkinter.RIGHT, fill = tkinter.Y, expand = 1)
key['yscrollcommand'] = self.yscroll.set
tkinter.Listbox.__init__(self, self.frame, **key)
self.yscroll.config(command = self.yview)
self.pack(side = tkinter.LEFT, fill = tkinter.BOTH, expand = 1)
# Copy geometry methods of self.frame
# for m in (tkinter.Pack.__dict__.keys() + tkinter.Grid.__dict__.keys() + tkinter.Place.__dict__.keys()):
# m[0] == '_' or m == 'config' or m == 'configure' or \
# setattr(self, m, getattr(self.frame, m))
#class tkEntry(tkinter.Entry):
[ドキュメント]
class tkEntry(tkinter.ttk.Entry):
"""
概要:
右クリックメニュー(カット、コピー、ペーストなど)が組み込まれたTkinterのエントリウィジェットです。
詳細説明:
tkinter.ttk.Entryを継承し、右クリック時に標準的なテキスト編集操作
(カット、コピー、ペースト、削除、すべて選択)を提供するコンテキストメニューを表示します。
"""
def __init__(self, parent = None, app = None, **kwargs):
"""
概要:
tkEntryウィジェットを初期化します。
引数:
:param parent: (オプション) エントリウィジェットの親。
:type parent: tkinter.Widget or None
:param app: (オプション) アプリケーションのインスタンス。メニューの親ウィンドウとして使用されます。
:type app: object or None
:param **kwargs: tkinter.ttk.Entryに渡される追加のキーワード引数。
:type **kwargs: dict
"""
self.app = app
self.default_fontfamily = "" #"Yu Gothic UI"
self.default_fontsize = 10
super().__init__(parent, **kwargs)
self.__create_menu()
self.__bind_event()
def __create_menu(self):
"""
概要:
右クリックメニュー(コンテキストメニュー)を作成します。
詳細説明:
カット、コピー、ペースト、削除、すべて選択の各コマンドが含まれます。
"""
font = (self.default_fontfamily, self.default_fontsize)
self.menu = tkinter.Menu(self.app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee",
activebackground = "#000000", activeforeground = "#ffffff")
self.menu.add_command(label = "Cut", command = self.__on_cut, font = font)
self.menu.add_command(label = "Copy", command = self.__on_copy, font = font)
self.menu.add_command(label = "Paste", command = self.__on_paste, font = font)
self.menu.add_command(label = "Delete", command = self.__on_delete, font = font)
self.menu.add_separator()
self.menu.add_command(label = "Select all", command = self.__on_select_all, font = font)
def __on_cut(self):
"""
概要:
選択されたテキストをカットするイベントハンドラです。
"""
self.event_generate("<<Cut>>")
def __on_copy(self):
"""
概要:
選択されたテキストをコピーするイベントハンドラです。
"""
self.event_generate("<<Copy>>")
def __on_paste(self):
"""
概要:
クリップボードのテキストをペーストするイベントハンドラです。
"""
self.event_generate("<<Paste>>")
def __on_delete(self):
"""
概要:
選択されたテキストを削除するイベントハンドラです。
"""
first = self.index(tkinter.SEL_FIRST)
last = self.index(tkinter.SEL_LAST)
# first = self.index("sel.first")
# last = self.index("sel.last")
self.delete(first, last)
def __on_select_all(self):
"""
概要:
エントリ内のすべてのテキストを選択するイベントハンドラです。
"""
self.select_range(0, "end")
def __bind_event(self):
"""
概要:
右クリックイベントをメニュー表示にバインドします。
"""
self.bind("<Button-3>", self.__do_popup)
def __do_popup(self, e):
"""
概要:
右クリック時にコンテキストメニューを表示します。
引数:
:param e: Tkinterイベントオブジェクト。
:type e: tkinter.Event
"""
try:
self.menu.tk_popup(e.x_root, e.y_root)
finally:
self.menu.grab_release()
[ドキュメント]
class tkSpinbox(tkinter.Spinbox):
"""
概要:
右クリックメニュー(カット、コピー、ペーストなど)が組み込まれたTkinterのSpinboxウィジェットです。
詳細説明:
tkinter.Spinboxを継承し、右クリック時に標準的なテキスト編集操作
(カット、コピー、ペースト、削除、すべて選択)を提供するコンテキストメニューを表示します。
"""
def __init__(self, parent = None, app = None, **kwargs):
"""
概要:
tkSpinboxウィジェットを初期化します。
引数:
:param parent: (オプション) Spinboxウィジェットの親。
:type parent: tkinter.Widget or None
:param app: (オプション) アプリケーションのインスタンス。メニューの親ウィンドウとして使用されます。
:type app: object or None
:param **kwargs: tkinter.Spinboxに渡される追加のキーワード引数。
:type **kwargs: dict
"""
self.app = app
self.default_fontfamily = "Yu Gothic UI"
self.default_fontsize = 10
super().__init__(parent, **kwargs)
self.__create_menu()
self.__bind_event()
def __create_menu(self):
"""
概要:
右クリックメニュー(コンテキストメニュー)を作成します。
詳細説明:
カット、コピー、ペースト、削除、すべて選択の各コマンドが含まれます。
"""
font = (self.default_fontfamily, self.default_fontsize)
self.menu = tkinter.Menu(self.app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee",
activebackground = "#000000", activeforeground = "#ffffff")
self.menu.add_command(label = "Cut", command = self.__on_cut, font = font)
self.menu.add_command(label = "Copy", command = self.__on_copy, font = font)
self.menu.add_command(label = "Paste", command = self.__on_paste, font = font)
self.menu.add_command(label = "Delete", command = self.__on_delete, font = font)
self.menu.add_separator()
self.menu.add_command(label = "Select all", command = self.__on_select_all, font = font)
def __on_cut(self):
"""
概要:
選択されたテキストをカットするイベントハンドラです。
"""
self.event_generate("<<Cut>>")
def __on_copy(self):
"""
概要:
選択されたテキストをコピーするイベントハンドラです。
"""
self.event_generate("<<Copy>>")
def __on_paste(self):
"""
概要:
クリップボードのテキストをペーストするイベントハンドラです。
"""
self.event_generate("<<Paste>>")
def __on_delete(self):
"""
概要:
選択されたテキストを削除するイベントハンドラです。
"""
first = self.index(tkinter.SEL_FIRST)
last = self.index(tkinter.SEL_LAST)
# first = self.index("sel.first")
# last = self.index("sel.last")
self.delete(first, last)
def __on_select_all(self):
"""
概要:
Spinbox内のすべてのテキストを選択するイベントハンドラです。
"""
self.select_range(0, "end")
def __bind_event(self):
"""
概要:
右クリックイベントをメニュー表示にバインドします。
"""
self.bind("<Button-3>", self.__do_popup)
def __do_popup(self, e):
"""
概要:
右クリック時にコンテキストメニューを表示します。
引数:
:param e: Tkinterイベントオブジェクト。
:type e: tkinter.Event
"""
try:
self.menu.tk_popup(e.x_root, e.y_root)
finally:
self.menu.grab_release()
[ドキュメント]
def RangeFrame(app, parent = None,
frame_args = {},
head_label_args = {},
x0_entry_args = {},
center_label_args = {},
x1_entry_args = {},
gridframe = None,
igridrow = None):
"""
概要:
2つのSpinbox(またはEntry)ウィジェットで範囲を指定するためのフレームウィジェットを作成します。
詳細説明:
ラベルと2つの入力フィールド(通常はtkSpinbox)を水平に配置し、範囲入力UIを提供します。
変数をapp.tkvarsに登録します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) フレームの親ウィジェット。
:type parent: tkinter.Widget or None
:param frame_args: (オプション) メインフレームのキーワード引数。
:type frame_args: dict
:param head_label_args: (オプション) 先頭のラベルウィジェットのキーワード引数。
:type head_label_args: dict
:param x0_entry_args: (オプション) 最初の入力フィールド(下限)のキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type x0_entry_args: dict
:param center_label_args: (オプション) 中央のラベルウィジェットのキーワード引数。
:type center_label_args: dict
:param x1_entry_args: (オプション) 2番目の入力フィールド(上限)のキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type x1_entry_args: dict
:param gridframe: (オプション) 既存のグリッドフレームを使用する場合、そのインスタンス。
:type gridframe: tkinter.Frame or None
:param igridrow: (オプション) gridframeを使用する場合の行インデックス。
:type igridrow: int or None
戻り値:
:returns: 作成された範囲入力フレームウィジェット。
:rtype: tkinter.Frame
"""
if gridframe:
frame = gridframe
else:
frame = tkinter.Frame(parent, **frame_args)
head_label = tkinter.Label(frame, **head_label_args)
head_label.grid(row = igridrow, column = 0, sticky = tkinter.W)
if 'textvariable' in x0_entry_args:
if type(x0_entry_args['textvariable']) is list:
x0_entry_args['textvariable'] \
= set_variable_from_list(app,
x0_entry_args['textvariable'][0], x0_entry_args['textvariable'][1])
# x0_entry = ttk.Entry(frame, **x0_entry_args)
x0_entry = tkSpinbox(parent = frame, app = app, **x0_entry_args)
# x0_entry = tkinter.Spinbox(frame, **x0_entry_args)
x0_entry.grid(row = igridrow, column = 1)
center_label = tkinter.Label(frame, **center_label_args)
center_label.grid(row = igridrow, column = 2, sticky = tkinter.E)
if 'textvariable' in x1_entry_args:
if type(x1_entry_args['textvariable']) is list:
x1_entry_args['textvariable'] \
= set_variable_from_list(app,
x1_entry_args['textvariable'][0], x1_entry_args['textvariable'][1])
# x1_entry = ttk.Entry(frame, **x1_entry_args)
x1_entry = tkSpinbox(parent = frame, app = app, **x1_entry_args)
# x1_entry = tkinter.Spinbox(frame, **x1_entry_args)
x1_entry.grid(row = igridrow, column = 3)
return frame
[ドキュメント]
def ComboboxFrame(app, parent,
frame_args = { 'bg': 'dim gray' },
head_label_args = { 'text': 'Input module:' },
combobox_args = { 'textvariable': ['varname', None], 'values': ['abc', 'def'], 'default': 'def', 'width': 10 },
tail_label_args = { 'text': '' },
gridframe = None,
igridrow = None,
is_print = False,
**kwargs
):
"""
概要:
ラベルとコンボボックスを組み合わせたフレームウィジェットを作成します。
詳細説明:
オプションで先頭と末尾にラベルを持ち、中央にコンボボックスを配置します。
コンボボックスのtextvariableはapp.tkvarsに登録されます。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: フレームの親ウィジェット。
:type parent: tkinter.Widget
:param frame_args: (オプション) メインフレームのキーワード引数。
:type frame_args: dict
:param head_label_args: (オプション) 先頭のラベルウィジェットのキーワード引数。
:type head_label_args: dict
:param combobox_args: (オプション) コンボボックスウィジェットのキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type combobox_args: dict
:param tail_label_args: (オプション) 末尾のラベルウィジェットのキーワード引数。
:type tail_label_args: dict
:param gridframe: (オプション) 既存のグリッドフレームを使用する場合、そのインスタンス。
:type gridframe: tkinter.Frame or None
:param igridrow: (オプション) gridframeを使用する場合の行インデックス。
:type igridrow: int or None
:param is_print: (オプション) デバッグ情報を出力するかどうか。
:type is_print: bool
:param **kwargs: その他のキーワード引数(現在使用されていません)。
:type **kwargs: dict
戻り値:
:returns: 作成されたコンボボックスフレームウィジェット。
:rtype: tkinter.Frame
"""
tkvars = app.tkvars
if gridframe and igridrow is not None:
frame = gridframe
else:
frame = tkinter.Frame(parent, **frame_args)
if head_label_args:
label = tkinter.Label(frame, **head_label_args)
if igridrow is not None:
label.grid(row = igridrow, column = 0)
else:
label.pack(side = tkinter.LEFT)
if combobox_args:
if 'textvariable' in combobox_args:
if type(combobox_args['textvariable']) is list:
key = combobox_args['textvariable'][0]
var = combobox_args['textvariable'][1]
combobox_args['textvariable'] = set_variable_from_list(app, key, var)
tkvars.set_attr(key, var)
else:
var = combobox_args['textvariable'] # 'combobx_args'を'combobox_args'に修正
cb = tkinter.ttk.Combobox(frame, textvariable = var,
values = combobox_args['values'], width = combobox_args['width'])
cb.set(combobox_args['default'])
cb.bind('<<ComboboxSelected>>', lambda e: print('var=%s' % var.get()))
if igridrow is not None:
cb.grid(row = igridrow, column = 1)
else:
cb.pack(side = tkinter.LEFT, anchor = 'w', padx = 2, pady = 2) #, expand = True, fill = tkinter.X)
"""
var = tkinter.StringVar(value = '')
cb = tkinter.ttk.Combobox(combobox_frame, textvariable = var,
values = combobox_args['values'], width = combobox_args['width'])
cb.set(combobox_args['default'])
cb.bind('<<ComboboxSelected>>', lambda e: print('var=%s' % var.get()))
cb.pack(side = tkinter.LEFT, expand = True, fill = tkinter.X)
combobox_frame.pack(side = tkinter.LEFT, anchor = 'w', padx = 2, pady = 2)
"""
if tail_label_args:
label2 = tkinter.Label(frame, **tail_label_args)
if igridrow is not None:
label2.grid(row = igridrow, column = 0)
else:
label2.pack(side = tkinter.LEFT)
frame.pack(side = tkinter.TOP, anchor = tkinter.W, padx = 2, pady = 2)
return frame
[ドキュメント]
def PathFrame(app, parent = None,
frame_args = {},
head_label_args = None,
entry_args = None,
combobox_args = None,
spinbox_args = None,
tail_label_args = None,
button_args = None,
save_button_args = None,
edit_button_args = None,
copy_button_args = None,
optionmenu_args = None,
menubotton_args = None,
shell_button_args = None,
font_size = 10,
gridframe = None,
grid_args = {},
igridrow = None,
is_print = False,
**kwargs):
"""
概要:
ファイルパスの入力、参照、編集、実行などをサポートする多機能なフレームウィジェットを作成します。
詳細説明:
ラベル、入力フィールド(Entry, Combobox, Spinboxのいずれか)、参照ボタン、
編集ボタン、コピーボタン、外部アプリケーション選択メニュー、シェル実行ボタンなどを組み合わせて、
ファイルパス関連のUIを構築します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) フレームの親ウィジェット。
:type parent: tkinter.Widget or None
:param frame_args: (オプション) メインフレームのキーワード引数。
:type frame_args: dict
:param head_label_args: (オプション) 先頭のラベルウィジェットのキーワード引数。
:type head_label_args: dict or None
:param entry_args: (オプション) Entryウィジェットのキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type entry_args: dict or None
:param combobox_args: (オプション) Comboboxウィジェットのキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type combobox_args: dict or None
:param spinbox_args: (オプション) Spinboxウィジェットのキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type spinbox_args: dict or None
:param tail_label_args: (オプション) 末尾のラベルウィジェットのキーワード引数。
:type tail_label_args: dict or None
:param button_args: (オプション) ファイル参照ボタンのキーワード引数。
:type button_args: dict or None
:param save_button_args: (オプション) ファイル保存ボタンのキーワード引数。
:type save_button_args: dict or None
:param edit_button_args: (オプション) 編集ボタンのキーワード引数。
:type edit_button_args: dict or None
:param copy_button_args: (オプション) パスをクリップボードにコピーするボタンのキーワード引数。
:type copy_button_args: dict or None
:param optionmenu_args: (オプション) 外部アプリケーション選択OptionMenuのキーワード引数。textvariableはリスト形式[key, var]で指定可能。
:type optionmenu_args: dict or None
:param menubotton_args: (オプション) Menubuttonウィジェットのキーワード引数(現在フレーム内で動作しません)。
:type menubotton_args: dict or None
:param shell_button_args: (オプション) シェル実行ボタンのキーワード引数。
:type shell_button_args: dict or None
:param font_size: (オプション) ウィジェットのフォントサイズ。
:type font_size: int
:param gridframe: (オプション) 既存のグリッドフレームを使用する場合、そのインスタンス。
:type gridframe: tkinter.Frame or None
:param grid_args: (オプション) グリッドレイアウトのキーワード引数。
:type grid_args: dict
:param igridrow: (オプション) gridframeを使用する場合の行インデックス。
:type igridrow: int or None
:param is_print: (オプション) デバッグ情報を出力するかどうか。
:type is_print: bool
:param **kwargs: path_button_clickなどに渡される追加のキーワード引数(ini_dir, file_typeなど)。
:type **kwargs: dict
戻り値:
:returns: 作成されたパスフレームウィジェット。
:rtype: tkinter.Frame
"""
tkvars = app.tkvars
if gridframe and igridrow is not None:
frame = gridframe
else:
frame = tkinter.Frame(parent, **frame_args)
if head_label_args and head_label_args.get("width", 1) != 0:
head_label_args['text'] = app.p(head_label_args['text'])
# print("\nhead_label_args=", head_label_args, "\n")
label = tkinter.Label(frame, **head_label_args)
# label = tk.Label(frame, text = head_text_args.get('text', ''))
if 'igridcol' in head_label_args.keys():
igridcol = head_label_args['igridcol']
del head_label_args["igridcol"]
else:
igridcol = 0
if igridrow is not None:
label.grid(row = igridrow, column = igridcol, **grid_args)
else:
label.pack(side = 'left')
if entry_args and entry_args.get("width", 0) != 0:
if 'textvariable' in entry_args:
if type(entry_args['textvariable']) is list:
key = entry_args['textvariable'][0]
val = entry_args['textvariable'][1]
entry_args['textvariable'] = set_variable_from_list(app, key, val)
tkvars.set_attr(key, val)
# entry = tkinter.ttk.Entry(frame, **entry_args)
entry = tkEntry(parent = frame, app = app, **entry_args)
if 'igridcol' in entry_args.keys():
igridcol = entry_args['igridcol']
del entry_args["igridcol"]
else:
igridcol = 1
if igridrow is not None:
entry.grid(row = igridrow, column = igridcol)
else:
entry.pack(side = 'left', expand = True, fill = tkinter.X)
frame.entry = entry
if combobox_args and combobox_args.get("width", 0) != 0:
if 'textvariable' in combobox_args:
if type(combobox_args['textvariable']) is list:
key = combobox_args['textvariable'][0]
val = combobox_args['textvariable'][1]
combobox_args['textvariable'] = set_variable_from_list(app, key, val)
tkvars.set_attr(key, val)
entry = tkinter.ttk.Combobox(frame, **combobox_args)
if 'igridcol' in combobox_args.keys():
igridcol = combobox_args['igridcol']
del combobox_args["igridcol"]
else:
igridcol = 1
if igridrow is not None:
entry.grid(row = igridrow, column = igridcol)
else:
entry.pack(side = 'left', expand = True, fill = tkinter.X)
frame.combobox = entry
if spinbox_args and spinbox_args.get("width", 0) != 0:
if 'textvariable' in spinbox_args:
if type(spinbox_args['textvariable']) is list:
key = spinbox_args['textvariable'][0]
val = spinbox_args['textvariable'][1]
spinbox_args['textvariable'] = set_variable_from_list(app, key, val)
tkvars.set_attr(key, val)
entry = tkSpinbox(parent = frame, app = app, **spinbox_args)
# entry = tkinter.Spinbox(frame, **spinbox_args)
if 'igridcol' in spinbox_args.keys():
igridcol = spinbox_args['igridcol']
del spinbox_args["igridcol"]
else:
igridcol = 1
if igridrow is not None:
entry.grid(row = igridrow, column = igridcol)
else:
entry.pack(side = 'left', expand = True, fill = tkinter.X)
frame.spinbox = entry
if tail_label_args and tail_label_args.get("width", 0) != 0:
tail_label_args['text'] = app.p(tail_label_args['text'])
label2 = tkinter.ttk.Label(frame, **tail_label_args)
# label2 = ttk.Label(frame, text = tail_text_args.get('text', ''))
if 'igridcol' in tail_label_args.keys():
igridcol = tail_label_args['igridcol']
del tail_label_args["igridcol"]
else:
igridcol = 2
if igridrow is not None:
label2.grid(row = igridrow, column = igridcol)
else:
label2.pack(side = 'left')
if kwargs.get('ini_dir', None) is None:
kwargs['ini_dir'] = '.'
if kwargs.get('file_type', None) is None:
kwargs['file_type'] = [('All', '*.*')]
if button_args and button_args.get("width", 0) != 0:
button_args['text'] = app.p(button_args['text'])
if button_args.get('command', None) is None:
button_args['command'] = lambda: path_button_click(app, entry_args['textvariable'])
if 'igridcol' in button_args.keys():
igridcol = button_args['igridcol']
del button_args["igridcol"]
else:
igridcol = 3
button = Button(app, frame, **button_args)
# button = tkinter.Button(frame, **button_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
frame.button = button
if save_button_args and save_button_args.get("width", 0) != 0:
save_button_args['text'] = app.p(save_button_args['text'])
if save_button_args.get('command', None) is None:
save_button_args['command'] = lambda: path_button_click(app, entry_args['textvariable'])
if 'igridcol' in save_button_args.keys():
igridcol = save_button_args['igridcol']
del save_button_args["igridcol"]
else:
igridcol = 4
button = Button(app, frame, **save_button_args)
# button = tkinter.Button(frame, **save_button_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
frame.save_button = button # save_button_argsを直接格納しないよう修正
if edit_button_args and edit_button_args.get("width", 0) != 0:
edit_button_args['text'] = app.p(edit_button_args['text'])
if edit_button_args.get('command', None) is None:
edit_button_args['command'] \
= lambda: edit_button_click(app, entry_args['textvariable'])
edit_button = Button(app, frame, **edit_button_args)
# edit_button = tkinter.Button(frame, **edit_button_args)
if igridrow is not None:
edit_button.grid(row = igridrow, column = 4)
else:
edit_button.pack(side = tkinter.LEFT)
frame.edit_button = edit_button
def copy_to_clipboard(parent, entry_variable):
print(f"copy [{entry_variable.get()}] to clipboard")
parent.clipboard_clear()
parent.clipboard_append(entry_variable.get())
parent.update_idletasks()
if copy_button_args:
copy_button_args['text'] = app.p(copy_button_args['text'])
if copy_button_args.get('command', None) is None:
copy_button_args['command'] \
= lambda: copy_to_clipboard(frame, entry_args['textvariable'])
if 'igridcol' in copy_button_args.keys():
igridcol = copy_button_args['igridcol']
del copy_button_args["igridcol"]
else:
igridcol = 3
button = Button(app, frame, **copy_button_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
frame.copy_button = button
if optionmenu_args and optionmenu_args.get("width", 0) != 0:
if type(optionmenu_args['textvariable']) is list:
key = optionmenu_args['textvariable'][0]
val = optionmenu_args['textvariable'][1]
optionmenu_args['textvariable'] = set_variable_from_list(app, key, val)
tkvars.set_attr(key, val)
if optionmenu_args.get('command', None) is None:
optionmenu_args['command'] \
= lambda: execute_command(app,
optionmenu_args['textvariable'].get(), entry_args['textvariable'].get(),
is_print = is_print)
def optionmenu_callback(*args):
optionmenu_args['command']()
values = optionmenu_args.get('values', ['notepad.exe'])
optionmenu = tkinter.OptionMenu(frame, optionmenu_args['textvariable'], *values)
optionmenu.configure(width = optionmenu_args.get('width', None))
optionmenu.configure(height = optionmenu_args.get('height', 1))
optionmenu.configure(font = optionmenu_args.get('font', None))
optionmenu_args['textvariable'].trace("w", optionmenu_callback)
# optionmenu.configure(command = optionmenu_args['command'])
if igridrow is not None:
optionmenu.grid(row = igridrow, column = 5)
else:
optionmenu.pack(side = tkinter.LEFT)
frame.optionmenu = optionmenu
# Menubottun does not work in frame
if menubotton_args and menubotton_args.get("width", 0) != 0:
# if menubotton_args.get('command', None) is None:
# menubotton_args['command'] \
# = lambda: optionmenu_selected(app, optionmenu_args['textvariable'])
menubotton = tkinter.Menubutton(frame, **menubotton_args)
menu = tkinter.Menu(menubotton)
menu.add_command(label="A")
menu.add_command(label="B")
menubotton["menu"] = menu
if igridrow is not None:
menubotton.grid(row = igridrow, column = 6)
else:
menubotton.pack(side = tkinter.LEFT)
frame.menubotton = menubotton
if shell_button_args and shell_button_args.get("width", 0) != 0:
shell_button_args['text'] = app.p(shell_button_args['text'])
if shell_button_args.get('command', None) is None:
shell_button_args['command'] \
= lambda: shell_button_click(app)
shell_button = Button(app, frame, **shell_button_args)
# shell_button = tkinter.Button(frame, **shell_button_args)
if igridrow is not None:
shell_button.grid(row = igridrow, column = 7)
else:
shell_button.pack(side = tkinter.LEFT)
frame.shell_button = shell_button
frame.pack(side = tkinter.TOP, anchor = tkinter.W, padx = 2, pady = 2, expand = True, fill = tkinter.X)
return frame
[ドキュメント]
def LabelEntryFrame(app, parent = None,
frame_args = {},
label_args = None,
entry_args = None,
combobox_args = None,
spinbox_args = None,
gridframe = None,
grid_args = {},
igridrow = 0):
"""
概要:
ラベルと入力フィールド(Entry, Combobox, Spinboxのいずれか)を組み合わせたフレームウィジェットを作成します。
詳細説明:
PathFrame関数を内部的に呼び出すことで、シンプルにラベルと単一の入力フィールドを配置するUIを提供します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) フレームの親ウィジェット。
:type parent: tkinter.Widget or None
:param frame_args: (オプション) メインフレームのキーワード引数。
:type frame_args: dict
:param label_args: (オプション) ラベルウィジェットのキーワード引数。PathFrameのhead_label_argsに相当。
:type label_args: dict or None
:param entry_args: (オプション) Entryウィジェットのキーワード引数。
:type entry_args: dict or None
:param combobox_args: (オプション) Comboboxウィジェットのキーワード引数。
:type combobox_args: dict or None
:param spinbox_args: (オプション) Spinboxウィジェットのキーワード引数。
:type spinbox_args: dict or None
:param gridframe: (オプション) 既存のグリッドフレームを使用する場合、そのインスタンス。
:type gridframe: tkinter.Frame or None
:param grid_args: (オプション) グリッドレイアウトのキーワード引数。
:type grid_args: dict
:param igridrow: (オプション) gridframeを使用する場合の行インデックス。
:type igridrow: int
戻り値:
:returns: 作成されたラベル・エントリフレームウィジェット。
:rtype: tkinter.Frame
"""
return PathFrame(app, parent = parent,
frame_args = frame_args,
head_label_args = label_args,
entry_args = entry_args,
combobox_args = combobox_args,
spinbox_args = spinbox_args,
gridframe = gridframe,
grid_args = grid_args,
igridrow = igridrow)
[ドキュメント]
def make_check_box(app, cparams, tkvars, parent, grid_frame,
varname = 'none',
label = None,
values = None,
defval = True, # Used to initialize entry
reset_val = True, # Used for 'set_default()'
help_text = None,
button1_args = None,
button2_args = None,
onvalue = True,
offvalue = False,
igridrow = 0):
"""
概要:
アプリケーションの設定値に基づいたチェックボックスウィジェットを作成します。
詳細説明:
app.tkvarsに管理されるtkinter.BooleanVarまたはtkinter.IntVarに関連付けられたチェックボックスと、
オプションで「デフォルト設定」ボタン、「ヘルプ」ボタンを配置します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param cparams: 設定パラメータを保持するオブジェクト。
:type cparams: object
:param tkvars: Tkinter変数を管理する辞書またはオブジェクト。
:type tkvars: object
:param parent: チェックボックスを配置する親ウィジェット。
:type parent: tkinter.Widget
:param grid_frame: チェックボックスを配置するグリッドフレーム。
:type grid_frame: tkinter.Frame
:param varname: (オプション) Tkinter変数の名前(キー)。デフォルトは'none'。
:type varname: str
:param label: (オプション) チェックボックスに表示するテキスト。Noneの場合、varnameが使用されます。
:type label: str or None
:param values: (オプション) 現在使用されていません。
:type values: any
:param defval: (オプション) tkvars[varname]の初期値。デフォルトはTrue。
:type defval: bool
:param reset_val: (オプション) 「デフォルト設定」ボタンがクリックされたときに設定される値。デフォルトはTrue。
:type reset_val: bool
:param help_text: (オプション) 「ヘルプ」ボタンがクリックされたときに表示されるヘルプテキスト。Noneの場合、f"help_{varname}"が使用されます。
:type help_text: str or None
:param button1_args: (オプション) 最初の追加ボタンのキーワード引数。commandとして'set_default'または'show_help'を指定可能。
:type button1_args: dict or None
:param button2_args: (オプション) 2番目の追加ボタンのキーワード引数。commandとして'set_default'または'show_help'を指定可能。
:type button2_args: dict or None
:param onvalue: (オプション) チェックされたときにvariableに設定される値。デフォルトはTrue。
:type onvalue: any
:param offvalue: (オプション) チェックが外されたときにvariableに設定される値。デフォルトはFalse。
:type offvalue: any
:param igridrow: (オプション) グリッドレイアウトを使用する場合の行インデックス。
:type igridrow: int
"""
def on_clicked():
# print("changed to ", tkvars[varname].get())
pass
def set_default():
tkvars[varname].set(reset_val)
def show_help(help_text):
help_text = app.p(help_text)
return dialog_okcancel(app, title = f'Help for {varname}', message = help_text)
if label is None:
label = varname
if help_text is None:
help_text = f"help_{varname}"
frame = grid_frame #tkinter.Frame(parent)
if type(onvalue) is int:
tkvars[varname] = tkinter.IntVar(value = cparams.get(varname, defval))
else:
tkvars[varname] = tkinter.BooleanVar(value = cparams.get(varname, defval))
check = CheckButton(app, parent = frame, variable = tkvars[varname], text = label,
onvalue = onvalue, offvalue = offvalue,
# command = on_clicked)
command = None)
igridcol = 0
if igridrow is not None:
check.grid(row = igridrow, column = igridcol)
igridcol += 1
else:
check.pack(side = 'left')
if button1_args:
if button1_args["command"] == 'set_default':
button1_args["command"] = lambda: set_default()
elif button1_args["command"] == 'show_help':
button1_args["command"] = lambda: show_help(help_text)
if 'igridcol' in button1_args.keys():
igridcol = button1_args['igridcol']
del button1_args["igridcol"]
else:
igridcol = 2
button1_args['text'] = button1_args.get('text', '')
button = Button(app, frame, **button1_args)
# button1_args['text'] = app.p(button1_args.get('text', ''))
# button = tkinter.Button(frame, **button1_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
if button2_args:
button2_args['text'] = app.p(button2_args.get('text', ''))
if button2_args["command"] == 'set_default':
button2_args["command"] = lambda: set_default()
elif button2_args["command"] == 'show_help':
button2_args["command"] = lambda: show_help(help_text)
if 'igridcol' in button2_args.keys():
igridcol = button2_args['igridcol']
del button2_args["igridcol"]
else:
igridcol = 3
button = Button(app, frame, **button2_args)
# button = tkinter.Button(frame, **button2_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
# frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)
[ドキュメント]
def make_var_box(app, cparams, tkvars, parent, grid_frame,
varname = 'none', vartype = 'int',
label = None,
values = None,
from_ = None, to = 100, increment = 10, box_width = 6,
defval = 0, # Used to initialize entry
reset_val = None, # Used for 'set_default()'
help_text = None,
button1_args = None,
button2_args = None,
igridrow = 0):
"""
概要:
アプリケーションの設定値に基づいた変数入力ウィジェット(Entry、Spinbox、Combobox)を作成します。
詳細説明:
app.tkvarsに管理されるtkinter.StringVar、IntVar、DoubleVarに関連付けられた入力フィールドと、
オプションで「デフォルト設定」ボタン、「ヘルプ」ボタンを配置します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param cparams: 設定パラメータを保持するオブジェクト。
:type cparams: object
:param tkvars: Tkinter変数を管理する辞書またはオブジェクト。
:type tkvars: object
:param parent: ウィジェットを配置する親ウィジェット。
:type parent: tkinter.Widget
:param grid_frame: ウィジェットを配置するグリッドフレーム。
:type grid_frame: tkinter.Frame
:param varname: (オプション) Tkinter変数の名前(キー)。デフォルトは'none'。
:type varname: str
:param vartype: (オプション) 変数の型 ('int', 'str', 'double', 'float')。デフォルトは'int'。
:type vartype: str
:param label: (オプション) ウィジェットのラベルテキスト。Noneの場合、varnameが使用されます。
:type label: str or None
:param values: (オプション) Comboboxで使用する値のリスト。Noneの場合、EntryまたはSpinboxが使用されます。
:type values: list or None
:param from_: (オプション) Spinboxの最小値。
:type from_: int or float or None
:param to: (オプション) Spinboxの最大値。デフォルトは100。
:type to: int or float
:param increment: (オプション) Spinboxの増分値。デフォルトは10。
:type increment: int or float
:param box_width: (オプション) 入力フィールドの幅。デフォルトは6。
:type box_width: int
:param defval: (オプション) tkvars[varname]の初期値。デフォルトは0。リストまたはタプルで設定オブジェクトとデフォルト値を指定することもできます。
:type defval: any
:param reset_val: (オプション) 「デフォルト設定」ボタンがクリックされたときに設定される値。Noneの場合、defvalが使用されます。
:type reset_val: any or None
:param help_text: (オプション) 「ヘルプ」ボタンがクリックされたときに表示されるヘルプテキスト。Noneの場合、f"help_{varname}"が使用されます。
:type help_text: str or None
:param button1_args: (オプション) 最初の追加ボタンのキーワード引数。commandとして'set_default'または'show_help'を指定可能。
:type button1_args: dict or None
:param button2_args: (オプション) 2番目の追加ボタンのキーワード引数。commandとして'set_default'または'show_help'を指定可能。
:type button2_args: dict or None
:param igridrow: (オプション) グリッドレイアウトを使用する場合の行インデックス。
:type igridrow: int
"""
if label is None:
label = varname
if type(defval) is list or type(defval) is tuple:
config = defval[0]
_defval = defval[1]
defval = config.get(varname, _defval)
if help_text is None:
help_text = f"help_{varname}"
if vartype == 'int':
tkvars[varname] = tkinter.IntVar(value = cparams.get(varname, defval))
textbox_args = {'spinbox_args':
{'textvariable': [varname, tkvars[varname]], 'width': box_width,
'from': from_, 'to': to, 'increment': increment}
}
elif vartype == 'str':
tkvars[varname] = tkinter.StringVar(value = cparams.get(varname, defval))
textbox_args = {'entry_args':
{'textvariable': [varname, tkvars[varname]], 'width': box_width}
}
elif vartype == 'double' or vartype == 'float':
tkvars[varname] = tkinter.DoubleVar(value = cparams.get(varname, defval))
if from_ is None:
textbox_args = {'entry_args':
{'textvariable': [varname, tkvars[varname]], 'width': box_width}
}
else:
textbox_args = {'spinbox_args':
{'textvariable': [varname, tkvars[varname]], 'width': box_width,
'from': from_, 'to': to, 'increment': increment}
}
if values is None:
frame = LabelEntryFrame(app, parent,
frame_args = {'bg': 'white'},
label_args = {'text': f'{label}:', 'anchor': tkinter.E},
# entry_args = {'textvariable': ['score_mode', tkvars["score_mode"]], 'width': box_width},
# combobox_args = None,
# spinbox_args = {'textvariable': [varname, tkvars[varname]], 'width': box_width,
# 'from': from_, 'to': to, 'increment': increment},
**textbox_args,
gridframe = grid_frame,
grid_args = { "sticky": tkinter.E },
igridrow = igridrow
)
elif from_ is not None:
frame = PathFrame(app, parent,
frame_args = {'bg': 'white'},
head_label_args = {'text': f'{label}:', 'anchor': tkinter.E},
entry_args = {'textvariable': [varname, tkvars[varname]], 'width': 6},
gridframe = grid_frame,
grid_args = { "sticky": tkinter.E },
igridrow = igridrow
)
else:
frame = PathFrame(app, parent,
frame_args = {'bg': 'white'},
head_label_args = {'text': f'{label}:', 'anchor': tkinter.E},
combobox_args = {'textvariable': [varname, tkvars[varname]],
'values': values,
'width': 6},
gridframe = grid_frame,
grid_args = { "sticky": tkinter.E },
igridrow = igridrow
)
def set_default():
tkvars[varname].set(reset_val)
def show_help(help_text):
help_text = app.p(help_text)
return dialog_okcancel(app, title = f'Help for {varname}', message = help_text)
if button1_args:
if button1_args["command"] == 'set_default':
button1_args["command"] = lambda: set_default()
elif button1_args["command"] == 'show_help':
button1_args["command"] = lambda: show_help(help_text)
if 'igridcol' in button1_args.keys():
igridcol = button1_args['igridcol']
del button1_args["igridcol"]
else:
igridcol = 2
button1_args['text'] = button1_args.get('text', '')
button = Button(app, frame, **button1_args)
# button1_args['text'] = app.p(button1_args.get('text', ''))
# button = tkinter.Button(frame, **button1_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
if button2_args:
button2_args['text'] = app.p(button2_args.get('text', ''))
if button2_args["command"] == 'set_default':
button2_args["command"] = lambda: set_default()
elif button2_args["command"] == 'show_help':
button2_args["command"] = lambda: show_help(help_text)
if 'igridcol' in button2_args.keys():
igridcol = button2_args['igridcol']
del button2_args["igridcol"]
else:
igridcol = 3
button = Button(app, frame, **button2_args)
# button = tkinter.Button(frame, **button2_args)
if igridrow is not None:
button.grid(row = igridrow, column = igridcol)
else:
button.pack(side = 'left')
frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)
# lamda関数を呼び出す際には、呼び出された時の変数の内容が反映される。
# 同じスコープで同じ変数でlamda関数を呼び出すと、最後に変数に代入された内容が反映されてしまう
# これを避けるため、lamda関数につかう変数は、局所的なスコープで1回のみ使う
# この関数は、lamda関数に使っている変数を局所化するために作った
[ドキュメント]
def make_path_frame(app, cparams, parent, grid_frame, tkvar, varname,
file_type = [("All", "*.*")], working_dir = '.',
open_command = None, post_open_command = None,
head_label_width = 10,
entry_width = 60,
button_width = 12,
button_args = None,
copy_button_args = None,
edit_button_width = 6,
edit_button_args = None,
shell_button_width = 2,
optionmenu_width = 3,
font_size = 10,
is_print = True, igridrow = 0):
"""
概要:
ファイルパスの入力、参照、外部アプリケーションでの開く操作などを可能にする複合ウィジェットフレームを作成します。
詳細説明:
PathFrameを内部的に利用し、パス変数、ファイル選択ダイアログ、外部エディタ/ビューアでの開く機能、
シェル実行機能などを組み合わせてパス管理UIを生成します。app.tkvarsにtkinter.StringVarを登録します。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param cparams: 設定パラメータを保持するオブジェクト。
:type cparams: object
:param parent: フレームの親ウィジェット。
:type parent: tkinter.Widget
:param grid_frame: フレームを配置するグリッドフレーム。
:type grid_frame: tkinter.Frame
:param tkvar: Tkinter変数を管理する辞書またはオブジェクト。
:type tkvar: object
:param varname: Tkinter変数の名前(キー)。
:type varname: str
:param file_type: (オプション) ファイル選択ダイアログに表示するファイルタイプのリスト。
:type file_type: list
:param working_dir: (オプション) コマンドを実行する際の作業ディレクトリ。パスが空の場合、varnameのパスから自動的に設定されます。
:type working_dir: str
:param open_command: (オプション) ファイル選択ボタンが押されたときに実行されるカスタムコマンド。
:type open_command: callable or None
:param post_open_command: (オプション) ファイル選択後に追加で実行されるカスタムコマンド。
:type post_open_command: callable or None
:param head_label_width: (オプション) 先頭ラベルの幅。
:type head_label_width: int
:param entry_width: (オプション) 入力フィールドの幅。
:type entry_width: int
:param button_width: (オプション) 参照ボタンの幅。
:type button_width: int
:param button_args: (オプション) 参照ボタンのキーワード引数。
:type button_args: dict or None
:param copy_button_args: (オプション) コピーボタンのキーワード引数。
:type copy_button_args: dict or None
:param edit_button_width: (オプション) 編集ボタンの幅。
:type edit_button_width: int
:param edit_button_args: (オプション) 編集ボタンのキーワード引数。
:type edit_button_args: dict or None
:param shell_button_width: (オプション) シェルボタンの幅。
:type shell_button_width: int
:param optionmenu_width: (オプション) オプションメニューの幅。
:type optionmenu_width: int
:param font_size: (オプション) フォントサイズ。
:type font_size: int
:param is_print: (オプション) デバッグ情報を出力するかどうか。
:type is_print: bool
:param igridrow: (オプション) グリッドレイアウトを使用する場合の行インデックス。
:type igridrow: int
"""
config = app.configparams
ctkvars = app.tkvars
tkvars = ctkvars.get_param_dict()
applications = app.get_external_apps([config.get("editor_path", None), config.get("vesta_path", None)])
varname_app = f'{varname}_app'
tkvars[varname] = tkinter.StringVar(value = cparams.get(varname, config.get(varname, "")))
tkvars[varname_app] = tkinter.StringVar(value = 'app')
if working_dir == '.':
path = tkvars[varname].get() # 'tkvar[varname]'を'tkvars[varname]'に修正
if path != '':
dirname, basename, filebody, ext = split_file_path(path)
working_dir = dirname
def button_click_func(app, cparams, entry, file_type = None, ini_dir = None):
if open_command:
open_command(app, cparams, file_type, ini_dir)
else:
# path_button_click(app, cparams, entry, file_type = file_type, ini_dir = ini_dir)
path_button_click(app, entry, file_type = file_type, ini_dir = ini_dir)
if post_open_command:
post_open_command(app, cparams)
if button_args is None:
button_args = {'text': 'open', "width": button_width, 'font': ("", font_size),
'command': lambda: button_click_func(app, cparams, tkvars[varname],
file_type = file_type, ini_dir = working_dir)
}
if edit_button_args is None:
edit_button_args = {'text': 'ed', "width": edit_button_width, 'font': ("", font_size)}
frame = PathFrame(app, parent,
frame_args = {'bg': 'white'},
head_label_args = {'text': f'{varname}:', 'width': head_label_width, 'anchor': tkinter.E, 'font': ("", font_size)},
entry_args = {'textvariable': [varname, tkvars[varname]], 'width': entry_width, 'font': ("", font_size)},
button_args = button_args,
edit_button_args = edit_button_args,
copy_button_args = copy_button_args,
optionmenu_args = {'textvariable': [varname_app, tkvars[varname_app]],
"width": optionmenu_width, 'font': ("", font_size),
'values': applications, 'anchor': tkinter.RIGHT,
'font': ("", font_size),
"command": lambda: execute_command(
app,
working_dir = working_dir, is_print = is_print,
command = tkvars[varname_app].get(),
files = [tkvars[varname].get()])
},
shell_button_args = {'text': 'sh', "width": shell_button_width, 'font': ("", font_size)},
font_size = font_size,
gridframe = grid_frame,
grid_args = { "sticky": tkinter.E },
igridrow = igridrow
)
# program_path_frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)
[ドキュメント]
def Editor(app, parent = None, frame_args = {},
head_label_args = {},
entry_args = {},
tail_label_args = None,
button_args = {},
eval_button_args = None,
**kwargs):
"""
概要:
パス入力フィールドとテキストエリアを組み合わせた簡単なエディタウィジェットを作成します。
詳細説明:
PathFrameとtkinter.Textを組み合わせて、ファイルパスの指定、外部ツールでの編集、
テキストエリアでの内容表示・編集を可能にします。テキストエリアには右クリックメニューも実装されています。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: (オプション) エディタウィジェットの親。
:type parent: tkinter.Widget or None
:param frame_args: (オプション) 全体を囲むフレームのキーワード引数。
:type frame_args: dict
:param head_label_args: (オプション) PathFrameの先頭ラベルのキーワード引数。
:type head_label_args: dict
:param entry_args: (オプション) PathFrameの入力フィールドのキーワード引数。
:type entry_args: dict
:param tail_label_args: (オプション) PathFrameの末尾ラベルのキーワード引数。
:type tail_label_args: dict or None
:param button_args: (オプション) PathFrameのファイル参照ボタンのキーワード引数。
:type button_args: dict
:param eval_button_args: (オプション) PathFrameのシェル実行ボタンのキーワード引数。
:type eval_button_args: dict or None
:param **kwargs: tkinter.Textウィジェットに渡される追加のキーワード引数。
:type **kwargs: dict
戻り値:
:returns: 作成されたエディタフレームウィジェット。
:rtype: tkinter.Frame
"""
frame = tkinter.Frame(parent, **frame_args)
path_frame = PathFrame(app, parent = frame, frame_args = frame_args,
head_label_args = head_label_args,
entry_args = entry_args,
tail_label_args = tail_label_args,
button_args = button_args,
shell_button_args = eval_button_args,
**kwargs)
path_frame.pack(side = tkinter.TOP, anchor = 'w')
text_frame = tkinter.Text(frame, **kwargs)
text_frame.pack(side = tkinter.TOP, anchor = 'w')
frame.PathFrame = path_frame
frame.entry = path_frame.__dict__.get("entry", None)
frame.eval_button = path_frame.__dict__.get("shell_button", None)
frame.Text = text_frame
def __on_cut(self_text_frame): # self_text_frameという仮引数名に変更
self_text_frame.event_generate("<<Cut>>")
def __on_copy(self_text_frame):
self_text_frame.event_generate("<<Copy>>")
def __on_paste(self_text_frame):
self_text_frame.event_generate("<<Paste>>")
def __on_delete(self_text_frame):
first = self_text_frame.index(tkinter.SEL_FIRST)
last = self_text_frame.index(tkinter.SEL_LAST)
# first = self.index("sel.first")
# last = self.index("sel.last")
self_text_frame.delete(first, last)
def __on_select_all(self_text_frame):
self_text_frame.tag_add(tkinter.SEL, "1.0", tkinter.END) # select_rangeはTextウィジェットには存在しない
self_text_frame.mark_set(tkinter.INSERT, "1.0") # カーソルを先頭に移動
self_text_frame.see(tkinter.INSERT) # 選択範囲が表示されるようにスクロール
def __do_popup(self_text_frame, e):
try:
self_text_frame.menu.tk_popup(e.x_root, e.y_root)
finally:
self_text_frame.menu.grab_release()
def __create_menu(parent):
font = ('', 10)
parent.menu = tkinter.Menu(app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee",
activebackground = "#000000", activeforeground = "#ffffff")
parent.menu.add_command(label = "Cut", command = lambda: __on_cut(parent), font = font)
parent.menu.add_command(label = "Copy", command = lambda: __on_copy(parent), font = font)
parent.menu.add_command(label = "Paste", command = lambda: __on_paste(parent), font = font)
parent.menu.add_command(label = "Delete", command = lambda: __on_delete(parent), font = font)
parent.menu.add_separator()
parent.menu.add_command(label = "Select all", command = lambda: __on_select_all(parent), font = font)
__create_menu(text_frame)
text_frame.bind("<Button-3>", lambda e: __do_popup(text_frame, e))
return frame
[ドキュメント]
class tkButton(tkinter.Button):
"""
概要:
テキストの内容に応じて外観を変化させるTkinterのカスタムボタンウィジェットです。
詳細説明:
tkinter.Buttonを継承し、set_textメソッドでボタンのテキストを設定する際に、
特定のプレフィックス(!!! )があれば取り消し線と赤色で表示し、
それ以外は標準的な外観に戻します。
"""
def __init__(self, master, app, **kwargs):
"""
概要:
tkButtonウィジェットを初期化します。
引数:
:param master: ボタンを配置する親ウィジェット。
:type master: tkinter.Widget
:param app: アプリケーションのインスタンス。テキストの国際化処理に使用されます。
:type app: object
:param **kwargs: tkinter.Buttonに渡される追加のキーワード引数。fontはデフォルト設定の復元に使用されます。
:type **kwargs: dict
"""
super().__init__(master, **kwargs)
self.app = app
# kwargsから'font'引数を取得し、存在しない場合はデフォルト値を設定
# デフォルトのフォントサイズを10ptに設定
default_font_tuple = nametofont('TkDefaultFont').actual()
self.default_font = kwargs.get('font', (default_font_tuple[0], 10))
[ドキュメント]
def set_text(self, caption):
"""
概要:
ボタンのテキストを設定し、内容に基づいてスタイルを調整します。
詳細説明:
テキストが"!!! "で始まる場合、取り消し線と赤色フォントを適用します。
それ以外の場合は、標準のフォントスタイルに戻します。
また、app.p()を介してテキストの国際化処理を行います。
引数:
:param caption: ボタンに表示するテキスト。
:type caption: str or None
"""
if caption is None:
caption = ''
m = re.match(r'\!\!\!\s+(\S.*)', caption)
if m:
caption = m.groups()[0]
# default_fontがタプルであることを前提に、サイズを維持してスタイルを変更
self.configure(font = (self.default_font[0], self.default_font[1], "normal", "roman", "normal", "overstrike"), fg = "red")
else:
# default_fontがタプルであることを前提に、サイズを維持してスタイルをデフォルトに戻す
self.configure(font = (self.default_font[0], self.default_font[1]), fg = "black")
# self.configure(font = ("", 8, "normal", "roman", "normal"), fg = "black")
if self.app: # captionをapp.p()で処理する前に設定する行を削除し、一度のみ設定
caption = self.app.p(caption)
self.configure(text = caption)
[ドキュメント]
def get_text(self):
"""
概要:
ボタンの現在のテキストを取得します。
戻り値:
:returns: ボタンに表示されているテキスト。
:rtype: str
"""
return self["text"]
[ドキュメント]
class tkSetupDialog(tkinter.simpledialog.Dialog):
"""
概要:
アプリケーションの設定を行うためのカスタムダイアログボックスです。
詳細説明:
外部エディタのパス、Pythonインタープリタのパス、シェル、ファイルマネージャー、
VESTAなどのパス設定や、終了時の確認、デバッグモードなどのブール値設定を行うための
入力フィールドとチェックボックスを生成します。widgets引数で表示する設定項目を制御します。
"""
def __init__(self, master, app = None, title = None,
entry_width = 30, button_width = 4, edit_button_width = 2, shell_button_width = 2,
widgets = 'enditor_path|confirm_on_exit|debug_mode',
font_size = 10,
is_print = False) -> None:
"""
概要:
tkSetupDialogを初期化します。
引数:
:param master: ダイアログの親ウィジェット。
:type master: tkinter.Widget
:param app: (オプション) アプリケーションのインスタンス。設定値の取得と保存、外部アプリケーションパスの取得に使用されます。
:type app: object or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param entry_width: (オプション) 入力フィールドの幅。デフォルトは30。
:type entry_width: int
:param button_width: (オプション) パス参照ボタンの幅。デフォルトは4。
:type button_width: int
:param edit_button_width: (オプション) 編集/実行ボタンの幅。デフォルトは2。
:type edit_button_width: int
:param shell_button_width: (オプション) シェルボタンの幅。デフォルトは2。
:type shell_button_width: int
:param widgets: (オプション) ダイアログに表示するウィジェットをカンマ区切りで指定する文字列。デフォルトは'enditor_path|confirm_on_exit|debug_mode'。
:type widgets: str
:param font_size: (オプション) ウィジェットのフォントサイズ。デフォルトは10。
:type font_size: int
:param is_print: (オプション) デバッグ情報を出力するかどうか。デフォルトはFalse。
:type is_print: bool
"""
self.app = app
self.entry_width = entry_width
self.button_width = button_width
self.edit_button_width = edit_button_width
self.shell_button_width = shell_button_width
self.widgets = widgets
self.font_size = font_size
self.is_print = is_print
config = self.app.configparams
config.editor_path = config.get('editor_path', '')
for prg in ['code', 'notepad.exe', 'vim', 'vi', 'xemacs', 'emacs']:
if config.editor_path == '':
config.editor_path = find_executable_path(prg, defval = '')
config.confirm_on_exit = config.get('confirm_on_exit', True)
# print("tkSetupDialog.__init__: confirm_on_exit:", config.confirm_on_exit)
config.confirm_on_exit = config.get('confirm_on_exit', True) # 2重に設定されているが、元のロジックを維持
if self.is_print:
print("editor_path: ", config.editor_path)
super().__init__(parent = master, title = title)
[ドキュメント]
def body(self, master) -> None:
"""
概要:
ダイアログの本体部分にウィジェットを生成します。
詳細説明:
設定項目に応じて、PathFrameやCheckButtonを配置します。
引数:
:param master: 親ウィジェット。
:type master: tkinter.Widget
"""
cparams = self.app.get_params()
tkvars = self.app.tkvars
config = self.app.configparams
font_size = self.font_size
if self.app is not None:
editor_paths = self.app.get_editors()
python3_paths = self.app.get_external_apps(["python", "python3"], use_default = False)
python2_paths = self.app.get_external_apps(["python2", "python2.exe"], use_default = False)
perl_paths = self.app.get_external_apps(["perl"], use_default = False)
vesta_paths = self.app.get_external_apps(["VESTA"], use_default = False)
shell_paths = self.app.get_shells()
filer_paths = self.app.get_filers()
start_paths = self.app.get_start_apps()
else:
editor_paths = []
python3_paths = []
python2_paths = []
perl_paths = []
vesta_paths = []
shell_paths = [] # elseブロックでも初期化が必要
filer_paths = [] # elseブロックでも初期化が必要
start_paths = [] # elseブロックでも初期化が必要
def make_path_frame(app, parent, head_label, var_name, var, values,
font_size, entry_width, button_width, edit_button_width):
"""
概要:
セットアップダイアログ内のパス設定用のPathFrameを作成するヘルパー関数です。
引数:
:param app: アプリケーションのインスタンス。
:type app: object
:param parent: PathFrameを配置する親ウィジェット。
:type parent: tkinter.Widget
:param head_label: 先頭ラベルに表示するテキスト。
:type head_label: str
:param var_name: Tkinter変数の名前。
:type var_name: str
:param var: 関連付けられるTkinter変数。
:type var: tkinter.Variable
:param values: Comboboxの選択肢リスト。
:type values: list
:param font_size: フォントサイズ。
:type font_size: int
:param entry_width: 入力フィールドの幅。
:type entry_width: int
:param button_width: パス参照ボタンの幅。
:type button_width: int
:param edit_button_width: 編集/実行ボタンの幅。
:type edit_button_width: int
戻り値:
:returns: 作成されたPathFrameウィジェット。
:rtype: tkinter.Frame
"""
path_frame = PathFrame(app, parent = parent,
frame_args = {'bg': 'white'},
head_label_args = {'text': head_label, 'anchor': tkinter.E, 'font': ("", font_size)},
# entry_args = {'textvariable': [varname, var],
# 'width': self.entry_width},
combobox_args = {'textvariable': [var_name, var],
'values': values,
'width': entry_width, 'font': ("", font_size)},
button_args = {'text': 'path', "width": button_width, 'font': ("", 8),
'command': lambda: path_button_click(self.app, var, # 直接varを渡すように修正
# 'command': lambda: path_button_click(self.app, tkvars.editor_path.get(),
file_type = [('Executables', ";".join(get_file_masks('executables')))],
ini_dir = '.')
},
edit_button_args = {'text': 'run', "width": edit_button_width, 'font': ("", 8),
'command': lambda: execute_command(self.app,
config.start_path + ' ' + var.get(), self.app.script_path, is_print = self.is_print)
},
shell_button_args = {'text': 'sh', "width": self.shell_button_width, 'font': ("", 8)},
igridrow = 0
)
return path_frame
tkvars.editor_path = tkinter.StringVar(value = config.editor_path)
if 'editor_path' in self.widgets:
path_frame = make_path_frame(self.app, parent = master,
head_label = 'Editor path:', var_name = 'editor_path', var = tkvars.editor_path,
values = editor_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.python3_path = tkinter.StringVar(value = config.get("python3_path", ""))
if 'python3_path' in self.widgets:
python3_path_frame = make_path_frame(self.app, parent = master,
head_label = 'python3 path:', var_name = 'python3_path', var = tkvars.python3_path,
values = python3_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.python2_path = tkinter.StringVar(value = config.get("python2_path", ""))
if 'python2_path' in self.widgets:
python2_path_frame = make_path_frame(self.app, parent = master,
head_label = 'python2 path:', var_name = 'python2_path', var = tkvars.python2_path,
values = python2_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.perl_path = tkinter.StringVar(value = config.get("perl_path", ""))
if 'perl_path' in self.widgets:
perl_path_frame = make_path_frame(self.app, parent = master,
head_label = 'perl path:', var_name = 'perl_path', var = tkvars.perl_path,
values = perl_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.start_path = tkinter.StringVar(value = config.get("start_path", ""))
if 'start_path' in self.widgets:
start_path_frame = make_path_frame(self.app, parent = master,
head_label = 'start path:', var_name = 'start_path', var = tkvars.start_path,
values = start_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.shell_path = tkinter.StringVar(value = config.get("shell_path", ""))
if 'shell_path' in self.widgets:
shell_path_frame = make_path_frame(self.app, parent = master,
head_label = 'shell path:', var_name = 'shell_path', var = tkvars.shell_path,
values = shell_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.filer_path = tkinter.StringVar(value = config.get("filer_path", ""))
if 'filer_path' in self.widgets:
filer_path_frame = make_path_frame(self.app, parent = master,
head_label = 'filer path:', var_name = 'filer_path', var = tkvars.filer_path,
values = filer_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
tkvars.vesta_path = tkinter.StringVar(value = config.get("vesta_path", ""))
if 'vesta_path' in self.widgets:
vesta_path_frame = make_path_frame(self.app, parent = master,
head_label = 'VESTA path:', var_name = 'vesta_path', var = tkvars.vesta_path,
values = vesta_paths,
font_size = font_size, entry_width = self.entry_width, button_width = self.button_width,
edit_button_width = self.edit_button_width)
if 'confirm_on_exit' in self.widgets:
confirm_on_exit_check = CheckButton(self.app, parent = master, text = 'Confirm on exit',
defvalue = config.confirm_on_exit, font = ("", font_size),
command = None, onvalue = True, offvalue = False)
confirm_on_exit_check.pack()
self.confirm_on_exit_check_var = confirm_on_exit_check.variable
if 'debug_mode' in self.widgets:
debug_mode_check = CheckButton(self.app, parent = master, text = 'Debug mode',
defvalue = config.debug, font = ("", font_size),
command = None, onvalue = True, offvalue = False)
debug_mode_check.pack()
self.debug_mode_check_var = debug_mode_check.variable
if 'show_log' in self.widgets:
show_log_check = CheckButton(self.app, parent = master, text = 'Show log on colsole',
defvalue = config.show_log, font = ("", font_size),
command = None, onvalue = True, offvalue = False)
show_log_check.pack()
self.show_log_check_var = show_log_check.variable
[ドキュメント]
def buttonbox(self):
"""
概要:
ダイアログの下部にあるOK/キャンセルボタンボックスを生成します。
"""
box = tkinter.Frame(self)
# self.button1 = tkinter.Button(box, text="Apply", width=10, command=self.ok, state=tkinter.DISABLED)
self.button1 = Button(self.app, box, text="Apply", width=10, command=self.ok)
# self.button1 = tkinter.Button(box, text="Apply", width=10, command=self.ok)
self.button1.pack(side=tkinter.LEFT, padx=5, pady=5)
self.button2 = Button(self.app, box, text="Cancel", width=10, command=self.cancel)
# self.button2 = tkinter.Button(box, text="Cancel", width=10, command=self.cancel)
self.button2.pack(side=tkinter.LEFT, padx=5, pady=5)
box.pack(fill = tkinter.BOTH, expand = True)
[ドキュメント]
def switchButtonState(self):
"""
概要:
Applyボタンの状態を切り替えます(無効/有効)。
"""
if self.button1['state'] == tkinter.DISABLED: # tk.DISABLEDをtkinter.DISABLEDに修正
self.button1['state'] = tkinter.NORMAL # tk.NORMALをtkinter.NORMALに修正
[ドキュメント]
def apply(self): # ダイアログボックスを閉じた後
"""
概要:
ダイアログが閉じられた後に、変更された設定値をアプリケーションのconfigparamsに適用します。
"""
cparams = self.app.get_params()
config = self.app.configparams
tkvars = self.app.tkvars
if 'confirm_on_exit' in self.widgets: # ウィジェットが存在する場合のみ値を適用
config.confirm_on_exit = self.confirm_on_exit_check_var.get()
if 'debug_mode' in self.widgets: # ウィジェットが存在する場合のみ値を適用
config.debug = self.debug_mode_check_var.get()
if 'show_log' in self.widgets: # ウィジェットが存在する場合のみ値を適用
config.show_log = self.show_log_check_var.get()
self.app.show_log = config.show_log
if 'editor_path' in self.widgets:
val = tkvars.editor_path.get().strip()
if val != '':
config.editor_path = val
self.app.env["tkEDITOR_PATH"] = val
if 'python3_path' in self.widgets:
val = tkvars.python3_path.get().strip()
if val != '':
self.app.python_path = val
config.python3_path = val
if 'python2_path' in self.widgets:
val = tkvars.python2_path.get().strip()
if val != '':
config.python2_path = val
if 'perl_path' in self.widgets:
val = tkvars.perl_path.get().strip()
if val != '':
self.app.perl_path = val
config.perl_path = val
if 'start_path' in self.widgets:
val = tkvars.start_path.get().strip()
if val != '':
config.start_path = val
if 'shell_path' in self.widgets:
val = tkvars.shell_path.get().strip()
if val != '':
config.shell_path = val
if 'filer_path' in self.widgets:
val = tkvars.filer_path.get().strip()
if val != '':
config.filer_path = val
if 'vesta_path' in self.widgets:
val = tkvars.vesta_path.get().strip()
if val != '':
config.vesta_path = val
if self.is_print:
print("Setup:")
print(" editor_path:", config.editor_path)
print(" confirm_on_exit:", config.confirm_on_exit)
[ドキュメント]
def add_widget(self, c):
"""
概要:
tkCustomDialog_by_config内で使用される、設定に基づいてGUIウィジェットを追加するヘルパー関数です。
詳細説明:
configオブジェクトによって定義されたウィジェットのタイプと引数に従って、
様々なTkinterウィジェット(タブ、ラベル、フレーム、ボタン、エントリ、テキスト、
チェックボックス、リストボックス、コンボボックス、ラジオボタン、Spinbox、
ファイル/ディレクトリ選択)を動的に生成し、ダイアログに配置します。
引数:
:param self: tkCustomDialog_by_configのインスタンス。
:type self: tkCustomDialog_by_config
:param c: ウィジェットの設定情報を含むオブジェクト。このオブジェクトはwidgetタイプ、var名、args(キーワード引数)、vars(値のリスト)などの属性を持ちます。
:type c: object
"""
app = self.app
svars = app.svars
def remove_keys(d, remove_keys):
d2 = d.copy()
for key in d.keys():
if key in remove_keys:
del d2[key]
return d2
def mk_label(config, key):
s = config.args.get(key, None)
if s is not None:
del config.args[key]
label = tkinter.Label(w, text = s)
label.pack(side = tkinter.LEFT, anchor = 'w')
def set_default(def_val, widget_var):
widget_var.set(def_val)
def show_help(help_text):
help_text = app.p(help_text)
return dialog_okcancel(self.app, title = 'Help', message = help_text)
def execute(self, app, command):
c = command.split(';')
app.print_warning("Execute from custom dialog button:")
for s in c:
app.print_warning(f" [{s}]")
app.s_engine.execute_a_command(app, app.get_params(), command, fp = None)
def button_callback(dialog, app, command):
dialog.update_vars()
if dialog.callback is not None:
dialog.callback(dialog.ret) # self.retをdialog.retに修正
execute(dialog, app, command)
def add_button(w, text, command, side = 'LEFT', anchor = 'w'):
if text is not None:
if command is not None:
widget = tkinter.Button(w, text = app.p(text),
command = lambda: button_callback(self, self.app, command))
widget.pack(side = side, anchor = anchor)
else:
widget = tkinter.Button(w, text = app.p(text))
widget.pack(side = side, anchor = anchor)
def make_font_dict(**kwargs):
d = {}
for s in ['text', 'fg', 'bg']:
if kwargs.get(s, None) is not None:
d[s] = kwargs.get(s, None)
style = kwargs.get('style', None) # sループの外で一度だけ取得
if style is not None:
d['font'] = tkinter.font.Font(
# family="Menlo",
# size=50,
weight = style,
# slant="italic",
# underline=True,
# overstrike=True,
)
return d
frame = self.current_frame
var_name = c.var
tkvar_prev = self.tkvars.get(var_name, None)
var_type = c.args.get("var_type", "str")
def_val = c.args.get("def_val", None)
if def_val is not None:
show_def_val = True
else:
show_def_val = False
def_val = ""
init_val = c.args.get("init_val", None)
if init_val is None:
# init_val = c.args.get(var_name, None)
init_val = svars.get(var_name, def_val)
options = c.vars
values = c.args.get('values', '').split('##')
if len(values) >= 1 and values[0] != '': # valuesが空文字列の場合に余計な要素を追加しない
options = values + options
options = list(dict.fromkeys(options))
entry_width = c.args.get("entry_width", 30)
button_text = c.args.get("button_text", "path")
help_text = c.args.get("help_text", None) #f"help_{c.var}")
ini_dir = c.args.get("initialdir", ".")
ft = c.args.get("file_type", None)
if ft is None:
file_type = [("All", "*.*")]
else:
file_type = []
if '//' in ft:
_aa = ft.split('//')
else:
_aa = [ft]
for key in _aa:
if ':' in key:
try:
name, ext = key.split(':')
file_type.append((name, ext))
except ValueError as e: # split()に特有の例外
print(f"Warning: ValueError in tktkinter.add_widget(): {e}")
print(f" key={key} _aa={_aa} ft={ft} ")
# exit()
except Exception as e: # その他の例外をキャッチ
print(f"Warning: Unexpected error in tktkinter.add_widget(): {e}")
print(f" ft={ft}")
# exit()
else:
name, ext = "given types", ft
file_type.append((name, ext))
file_type.append(("All", "*.*"))
if 0: # デバッグ出力部分は元のロジックを維持
print(f"{c.widget}")
print(f" var_name={var_name}")
print(f" var={c.args.get(var_name, 'None')}")
print(f" args={c.args}")
print(f" vars={c.vars}")
print(f" init_val={init_val}")
print(f" def_val={def_val}")
# Add a widget group
for _ in [1]:
if c.widget == 'dummy':
pass
elif c.widget == 'tab':
if self.__dict__.get('notebook', None) is None or self.notebook is None:
self.notebook = tkinter.ttk.Notebook(self.root_frame)
self.notebook.pack(expand = True, fill = tkinter.X, side = tkinter.TOP, padx = 20)
# self.notebook.pack(expand = True, fill = tkinter.BOTH, side = tkinter.TOP)
self.bottom_frame = tkinter.Frame(frame)
self.bottom_frame.pack(side = tkinter.TOP)
self.current_frame = tkinter.Frame(self.notebook)
frame = self.current_frame
frame.pack(expand = True, fill = tkinter.X)
args2 = remove_keys(c.args, [])
if args2.get('text', None) is not None:
args2['text'] += ' '
# print("c.widget=tab: args2=", args2)
self.notebook.add(frame, **args2)
elif c.widget == 'reset_tab':
self.current_frame = self.root_frame
elif c.widget == 'label':
style = c.args.get('style', 'normal')
size = c.args.get('size', None)
font = tkinter.font.Font(
# family="Menlo",
# size=50,
weight = style,
# slant="italic",
# underline=True,
# overstrike=True,
)
args2 = remove_keys(c.args, ["label_head", "label_head_fg", "label_head_style","label_head_bg",
"label_tail", "def_val", "help_text",
"style", "size", "color"])
w = tkinter.Label(frame, font = font, **args2)
w.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.X)
elif c.widget == 'frame':
self.frame = tkinter.Frame(frame, **c.args)
elif c.widget == 'pack_frame':
if self.frame is None:
app.print_warning("Error in tktkinter.add_widget(): None frame")
continue
# self.frame.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.BOTH)
self.frame.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.X)
self.frame = None
elif c.widget == 'button':
command = c.args.get("command", "")
label_head = c.args.get("label_head", None)
label_tail = c.args.get("label_tail", None)
if self.frame:
w = self.frame
else:
w = tkinter.Frame(frame)
args2 = remove_keys(c.args, ["command", "help_text",
"label_head", "label_head_fg", "label_head_gg", "label_head_style", "label_tail"]) # label_head_ggはtypoと思われるが元のロジックを維持
text = args2.get('text', None)
if text is not None:
args2['text'] = app.p(text)
if label_head is not None:
d = make_font_dict(text = label_head, fg = c.args.get("label_head_fg", None),
bg = c.args.get("label_head_bg", None),
style = c.args.get("label_head_style", None))
widget = tkinter.Label(w, **d)
widget.pack(side = tkinter.LEFT)
if command and command != "":
widget = tkinter.Button(w, command = lambda: button_callback(self, self.app, command), **args2)
# widget = tkinter.Button(w, command = lambda: execute(self, self.app, command), **args2)
widget.pack(side = tkinter.LEFT)
else:
widget = tkinter.Button(w, **args2)
widget.pack(side = tkinter.LEFT)
if label_tail is not None:
d = make_font_dict(text = label_tail, fg = c.args.get("label_tail_fg", None),
bg = c.args.get("label_tail_bg", None),
style = c.args.get("label_tail_style", None))
widget = tkinter.Label(w, **d)
widget.pack(side = tkinter.LEFT)
if not self.frame:
w.pack(side = tkinter.TOP, anchor = 'w')
elif c.widget == 'message':
w = tkinter.Frame(frame)
family = c.args.get('family', None)
weight = c.args.get('weight', None)
size = c.args.get('size', None)
args2 = remove_keys(c.args, ["family", "weight", "size"])
if family is not None or weight is not None or size is not None:
def_font = Font(font = "TkDefaultFont")
if family is not None:
def_font['family'] = family # 'faimily'を'family'に修正
if weight is not None:
def_font['weight'] = weight
if size is not None:
def_font['size'] = size
args2["font"] = def_font #(def_font['family'], def_font['size'], def_font[')
widget = tkinter.Message(w, **args2)
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
elif c.widget == 'entry':
if self.frame:
w = self.frame
else:
w = tkinter.Frame(frame)
side = c.args.get('side', None)
anchor = c.args.get('anchor', None)
expand = c.args.get('expand', False)
args2 = remove_keys(c.args, ["label_head", "label_tail", "def_val", "init_val", "help_text",
"style", "size", "color", "side", "anchor", "expand"])
val = svars.get(var_name, '')
if tkvar_prev:
var = tkvar_prev
else:
var = tkinter.StringVar(value = val)
mk_label(c, "label_head")
self.tkvars.set(var_name, var)
widget = tkinter.Entry(w, textvariable = var, **args2)
# var.set(val) # set()後にdelete/insertするのは不要なのでコメントアウト
# widget.delete(0,"end") # set()後にdelete/insertするのは不要なのでコメントアウト
# widget.insert(0, val) # set()後にdelete/insertするのは不要なのでコメントアウト
if 'width' in args2:
widget.pack(side = tkinter.LEFT, anchor = 'w')
else:
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
mk_label(c, "label_tail")
if help_text and help_text != '':
button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
button.pack(side = tkinter.LEFT)
if show_def_val:
button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
button.pack(side = tkinter.LEFT)
if side is None:
side = tkinter.TOP
if anchor is None:
anchor = 'w'
w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)
elif c.widget == 'text':
w = tkinter.Frame(frame)
mk_label(c, "label_head")
args2 = remove_keys(c.args, ["label_head", "label_tail", "text"])
widget = tkinter.Text(w, **args2)
# self.vars[c.var] = tkinter.StringVar() を直接Textウィジェットに紐づける形に変更
# Textウィジェット自体をself.varsに格納し、Modifiedイベントで内容を同期させる
self.vars[c.var] = widget
widget.insert("1.0", c.args.get("text", ""))
def sync_text(event, var_name=c.var, text_widget=widget):
# Textウィジェットの内容をself.retに同期させるための仮実装
# 最終的な値はapply()で取得されるため、ここでは直接変更しない
pass
widget.bind("<<Modified>>", sync_text)
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
mk_label(c, "label_tail")
w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
"""
elif c.widget == 'text':
w = tkinter.Frame(frame)
mk_label(c, "label_head")
args2 = remove_keys(c.args, ["label_head", "label_tail", "text"])
widget = tkinter.Text(w, **args2)
self.vars[c.var] = widget
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
# widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.BOTH)
widget.insert(0., c.args.get("text", ""))
mk_label(c, "label_tail")
w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
# w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
"""
elif c.widget == 'checkbox':
if self.frame:
w = self.frame
else:
w = tkinter.Frame(frame)
side = c.args.get('side', None)
anchor = c.args.get('anchor', None)
args2 = remove_keys(c.args, ["label_head", "label_tail", "def_val", "init_val", "help_text",
"style", "size", "color", "side", "anchor"])
if init_val is True or pint(init_val) != 0:
init_val = True
else:
init_val = False
var = tkinter.BooleanVar(value = init_val)
self.tkvars.set(var_name, var)
mk_label(c, "label_head")
widget = tkinter.Checkbutton(w, variable = self.tkvars.get(var_name), **args2)
widget.pack(side = tkinter.LEFT, anchor = 'w')
if show_def_val:
button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
button.pack(side = tkinter.LEFT)
if help_text:
button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
button.pack(side = tkinter.LEFT)
mk_label(c, "label_tail")
if side is None:
side = tkinter.TOP
if anchor is None:
anchor = 'w'
w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)
elif c.widget == 'listbox' or c.widget == 'combobox' or c.widget == 'radiobutton':
if self.frame:
w = self.frame
else:
w = tkinter.Frame(frame)
side = c.args.get('side', None)
anchor = c.args.get('anchor', None)
expand = c.args.get('expand', False)
args2 = remove_keys(c.args,
["label_head", "label_tail", "text", "def_val", "init_val", "values",
"style", "size", "color", "side", "anchor", "expand"])
for i in range(10):
args2 = remove_keys(args2, [f"button{i+1}_text", f"button{i+1}_command"])
init_index = None
# Search index by removing comment
if init_val is None:
init_val = 0
init_index = 0
elif init_val == '':
init_index = 0
else:
init_val_nosp = f"{init_val}".replace(' ', '').replace('[', '(').replace(']', ')').lower()
for idx in range(len(options)):
s = options[idx]
s2 = re.sub(r'\s*#.*$', '', s).strip()
# print("idx=", var_name, idx, init_val, s, s2)
if init_val == s2 or init_val == s:
init_val = s
init_index = idx
break
s2_nosp = s2.replace(' ', '').replace('[', '(').replace(']', ')').lower()
if init_val_nosp == s2_nosp:
init_val = s
init_index = idx
break
# Case that init_index specifies the index number
if init_index is None:
v = pint(init_val, defval = None, strict = True)
if v is not None and 0 <= v < len(options):
init_index = v
init_val = options[v]
if init_index is None:
init_val = '*** choose ***'
mk_label(c, "label_head")
if c.widget == 'listbox':
lbvar = tkinter.StringVar(value = options)
widget = tkScrolledListbox(w, listvariable = lbvar, **args2)
widget.frame.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
if init_index is not None:
widget.selection_set(init_index)
widget.see(init_index)
self.tkvars.set(var_name, widget)
self.widget_vars.set(var_name, widget)
for i in range(10):
add_button(w, c.args.get(f"button{i+1}_text", None),
c.args.get(f"button{i+1}_command", None),
tkinter.LEFT, 'nw')
elif c.widget == 'combobox':
def add_combobox(self_dialog, w_parent, var_name, options_list, args_dict): # 引数名を変更してスコープを明確化
# cbvar = tkinter.StringVar(value = init_val)
# widget = tkinter.ttk.Combobox(w, textvariable = cbvar, **args2)
widget = tkinter.ttk.Combobox(w_parent, values = options_list, **args_dict)
# self.tkvars.set(var_name, widget) の位置を修正(widget.set()より前)
self_dialog.tkvars.set(var_name, widget)
# var = tkinter.StringVar(value = init_val)
# widget = tkinter.ttk.Combobox(w, values = c.vars, textvariable = var, **args2)
widget.set(init_val)
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
self_dialog.widget_vars.set(var_name, widget)
# args2 = remove_keys(c.args, ["option"])
add_combobox(self, w, var_name, options, args2) # スコープを明確化した引数名を渡す
for i in range(10):
add_button(w, c.args.get(f"button{i+1}_text", None),
c.args.get(f"button{i+1}_command", None),
tkinter.LEFT, 'nw')
elif c.widget == 'radiobutton':
var = tkinter.IntVar(value = init_index)
self.tkvars.set(var_name, var)
for i in range(len(options)):
s = options[i]
radio = tkinter.Radiobutton(w, text = s, value = i, variable = self.tkvars.get(var_name))
radio.pack(side = tkinter.TOP, anchor = 'w')
mk_label(c, "label_tail")
if side is None:
side = tkinter.TOP
if anchor is None:
anchor = 'w'
w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)
elif c.widget == 'spinbox':
if self.frame:
w = self.frame
else:
w = tkinter.Frame(frame)
side = c.args.get('side', None)
anchor = c.args.get('anchor', None)
args2 = remove_keys(c.args, ["label_head", "label_tail", "text", "var_type",
"help_text", "entry_width", "def_val", "init_val",
"side", "anchor"])
if tkvar_prev:
var = tkvar_prev
else:
if var_type == 'int':
var = tkinter.IntVar(value = init_val)
elif var_type == 'str':
var = tkinter.StringVar(value = init_val)
elif var_type == 'double' or var_type == 'float':
var = tkinter.DoubleVar(value = init_val)
else:
print(f"\nError in tktkinter.add_widget(): Invalid var_type [{var_type}]")
continue
self.tkvars.set(var_name, var)
mk_label(c, "label_head")
widget = tkSpinbox(parent = w, app = self.app,
textvariable = self.tkvars.get(var_name), width = entry_width, **args2)
# widget = tkinter.Entry(w, textvariable = var, **args2)
widget.pack(side = tkinter.LEFT, anchor = 'w') #, expand = True, fill = tkinter.X)
self.widget_vars.set(var_name, widget)
mk_label(c, "label_tail")
if show_def_val:
button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
button.pack(side = tkinter.LEFT)
if help_text and help_text != '':
button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
button.pack(side = tkinter.LEFT)
if side is None:
side = tkinter.TOP
if anchor is None:
anchor = 'w'
w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)
elif c.widget == 'choose_file' or c.widget == 'choose_dir':
show_path_button = c.args.get("show_path_button", 1)
show_option_button = c.args.get("show_option_button", 1)
example_data = c.args.get("example_data", None)
entry_type = c.args.get("entry_type", "combobox")
# entry_type = c.args.get("entry_type", "entry")
entry_filemask = c.args.get("entry_filemask", "*;*.*")
entry_height = c.args.get("entry_height", 5)
side = c.args.get("side", None)
anchor = c.args.get("anchor", None)
check_exist = c.args.get("check_exist", 1)
args2 = remove_keys(c.args, ["label_head", "label_tail", "text",
"button_text", "var_type",
"from", "to", "increment", "entry_width",
"show_path_button", "show_option_button", "show_data", "example_data",
"def_val", "init_val", "file_type", "initialdir",
"entry_type", "entry_filemask",
"side", "anchor", "check_exist"])
for i in range(10):
args2 = remove_keys(args2, [f"button{i+1}_text", f"button{i+1}_command"])
w = tkinter.Frame(frame)
mk_label(c, "label_head")
def combobox_sel_changed(e, cb, entry_filemask, entry_type): # 引数名を変更してスコープを明確化
cur_path = cb.get()
if not os.path.isdir(cb.prev_val):
dirname, basename, filebody, ext = split_file_path(cb.prev_val)
else:
dirname = cb.prev_val
m = re.match(r'\[(.*)\]$', cur_path)
isdir = False
if m:
cur_path = m.groups()[0]
isdir = True
new_path = os.path.join(dirname, cur_path)
if isdir:
dirs, files = get_file_list(new_path, entry_filemask)
for f in files:
dirs.append(f)
if 'combo' in entry_type:
cb['values'] = dirs
else:
pass
new_path = os.path.normpath(new_path)
cb.set(new_path)
cb.prev_val = new_path
# 'optiosn'を'options'に修正
if entry_type == 'entry':
if tkvar_prev:
entry_var = tkvar_prev
else:
entry_var = tkinter.StringVar(value = init_val)
self.tkvars.set(var_name, entry_var)
entry = tkinter.Entry(w, textvariable = self.tkvars.get(var_name),
width = entry_width, **args2)
else:
dirs, files = get_file_list(init_val, entry_filemask)
for f in files:
dirs.append(f)
if 'combo' in entry_type:
entry = tkinter.ttk.Combobox(w, values = dirs, width = entry_width, **args2)
entry.bind('<<ComboboxSelected>>', lambda e: combobox_sel_changed(e, entry, entry_filemask, entry_type))
entry.set(init_val)
else:
v = tkinter.StringVar(value = dirs)
entry = tkinter.Listbox(w, listvariable = v, width = entry_width, height = entry_height) # tkScrolledListboxの使用を修正
entry = tkScrolledListbox(w, listvariable = v, width = entry_width, height = entry_height, **args2) # heightも追加
entry.bind('<<ListboxSelected>>', lambda e: combobox_sel_changed(e, entry, entry_filemask, entry_type))
self.tkvars.set(var_name, entry)
entry.prev_val = init_val
entry_var = entry
entry.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
mk_label(c, "label_tail")
if c.widget == 'choose_file':
callback = lambda: path_button_click(self.app, entry_var,
ini_dir = ini_dir, file_type = file_type, entry_type = entry_type, check_exist = check_exist)
else:
callback = lambda: folder_button_click(self.app, entry_var,
ini_dir = ini_dir, entry_type = entry_type)
if show_path_button:
button = Button(self.app, w, text = self.app.p(button_text), command = callback)
button.pack(side = tkinter.LEFT)
if show_option_button:
config = self.app.config
# applications = self.app.get_external_apps([])
applications = self.app.get_external_apps([config.get("editor_path", None), config.get("vesta_path", None)])
applications = list(dict.fromkeys(applications))
var2 = tkinter.StringVar(value = 'app')
optionmenu = tkinter.OptionMenu(w, var2, *applications,
command = lambda e: execute_command(
self.app,
working_dir = '.',
command = var2.get(),
files = [self.tkvars.get(var_name).get()]
)
)
optionmenu.configure(width = 3)
optionmenu.configure(height = 1)
optionmenu.configure(font = ("", 10))
optionmenu.pack(side = tkinter.LEFT)
if example_data is not None:
config = self.app.config
data_list = example_data.split('#')
var3 = tkinter.StringVar(value = 'example')
def set_example_data(e, var):
val = var.get()
if val != 'example':
entry_var.set(val)
optionmenu = tkinter.OptionMenu(w, var3, *data_list,
command = lambda e: set_example_data(e, var3)
)
optionmenu.configure(width = 7)
optionmenu.configure(height = 1)
optionmenu.configure(font = ("", 10))
optionmenu.pack(side = tkinter.LEFT)
for i in range(10):
add_button(w, c.args.get(f"button{i+1}_text", None),
c.args.get(f"button{i+1}_command", None),
tkinter.LEFT, 'w')
if side is None:
side = tkinter.TOP
if anchor is None:
anchor = 'w'
w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)
else:
print("")
print( "#=========================================================================================")
print(f"# Warning in tktkinter.add_widget(): Invalid widget [{c.widget}]")
print( "#=========================================================================================")
print("")
[ドキュメント]
class tkdialog_yesno(tkinter.simpledialog.Dialog):
"""
概要:
「はい」/「いいえ」ボタンを持つシンプルなカスタムダイアログです。
詳細説明:
指定されたメッセージを表示し、ユーザーに「はい」または「いいえ」を選択させます。
結果はself.retに保存されます。
"""
def __init__(self, master, title = None, message = None, width = 80):
"""
概要:
tkdialog_yesnoを初期化します。
引数:
:param master: ダイアログの親ウィジェット。
:type master: tkinter.Widget
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str or None
:param width: (オプション) メッセージ表示領域の幅。デフォルトは80。
:type width: int
"""
self.ret = None
self.message = message
self.width = width
super().__init__(parent = master, title = title)
[ドキュメント]
def body(self, master) -> None:
"""
概要:
ダイアログの本体部分にメッセージラベルを生成します。
引数:
:param master: 親ウィジェット。
:type master: tkinter.Widget
"""
frame = tkinter.Frame(master)
frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
widget = tkinter.Label(master, text = self.message, width = self.width)
widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
[ドキュメント]
def buttonbox(self):
"""
概要:
ダイアログの下部にある「はい」/「いいえ」ボタンボックスを生成します。
"""
frame = tkinter.Frame(self)
w = tkinter.Button(frame, text = "Yes", width = 10, command = self.ok, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Return>", self.ok)
w = tkinter.Button(frame, text = "No", width = 10, command = self.cancel, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Escape>", self.cancel)
frame.pack()
[ドキュメント]
class tkCustomDialog_by_config(tkinter.simpledialog.Dialog):
"""
概要:
設定オブジェクトに基づいて動的にウィジェットを生成するカスタムダイアログです。
詳細説明:
外部から渡された設定オブジェクト(ウィジェットのタイプ、変数名、引数などを含むリスト)を解析し、
タブ、ラベル、フレーム、ボタン、エントリ、テキスト、チェックボックス、リストボックス、コンボボックス、
ラジオボタン、Spinbox、ファイル/ディレクトリ選択ウィジェットなどを動的に作成して配置します。
属性:
app: アプリケーションのインスタンス。
ret: ダイアログの戻り値。
config: ウィジェット構成を定義するオブジェクトのリスト。
buttons: ダイアログに表示するボタンの種類。
modeless: ダイアログがモーダルかモデルレスかを示すフラグ。
callback: ダイアログが閉じられた後に呼び出されるコールバック関数。
is_print: デバッグ情報を出力するかどうか。
vars: 動的に生成されたウィジェットに関連付けられたPython変数。
tkvars: 動的に生成されたウィジェットに関連付けられたTkinter変数。
widget_vars: 生成されたウィジェット自体を格納するオブジェクト。
frame: 現在のフレームウィジェット。
"""
def __init__(self, master, app = None, title = None, config = None, buttons = None, modeless = False, callback = None, is_print = False):
"""
概要:
tkCustomDialog_by_configを初期化します。
引数:
:param master: ダイアログの親ウィジェット。
:type master: tkinter.Widget
:param app: (オプション) アプリケーションのインスタンス。変数の管理や翻訳、コマンド実行などに使用されます。
:type app: object or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param config: (オプション) ウィジェット構成を定義するオブジェクトのリスト。各オブジェクトはwidgetタイプ、var名、args(キーワード引数)、vars(値のリスト)などを含む。
:type config: list or None
:param buttons: (オプション) ダイアログに表示するボタンの種類(例: "okcancel", "yesno", "close")。デフォルトは"okcancel"。
:type buttons: str or None
:param modeless: (オプション) ダイアログをモーダルとして開くか(Trueでモデルレス)。デフォルトはFalse。
:type modeless: bool
:param callback: (オプション) ダイアログが閉じられた後に呼び出されるコールバック関数。
:type callback: callable or None
:param is_print: (オプション) デバッグ情報を出力するかどうか。デフォルトはFalse。
:type is_print: bool
"""
self.app = app
self.ret = None
self.config = config
if buttons:
self.buttons = buttons.lower()
else:
self.buttons = "okcancel"
self.modeless = modeless
# if not modeless:
# self.grab_set()
self.callback = callback
self.is_print = is_print
self.vars = {}
self.tkvars = tkParams()
self.widget_vars = tkParams()
# print("app=", app)
if app:
# print("set vars to app")
app.dialog_vars = self.vars
app.dialog_tkvars = self.tkvars
app.dialog_widget_vars = self.widget_vars
self.frame = None
super().__init__(parent = master, title = title)
[ドキュメント]
def body(self, master) -> None:
"""
概要:
ダイアログの本体部分にウィジェットを生成します。
詳細説明:
設定オブジェクトself.configに基づいて、add_widget関数を呼び出して
各ウィジェットを動的に配置します。
引数:
:param master: 親ウィジェット。
:type master: tkinter.Widget
"""
config = self.config
# print("config=", config)
frame = tkinter.Frame(master)
frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
# frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
self.root_frame = frame
self.current_frame = frame
for c in config:
add_widget(self, c)
[ドキュメント]
def buttonbox(self):
"""
概要:
ダイアログの下部にあるボタンボックスを生成します。
詳細説明:
self.buttonsの設定に基づいて、「OK」、「キャンセル」、「はい」、「いいえ」、「閉じる」ボタンを配置します。
"""
frame = tkinter.Frame(self)
if "ok" in self.buttons:
w = tkinter.Button(frame, text = "OK", width = 10, command = self.ok, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Return>", self.ok)
if "cancel" in self.buttons:
w = tkinter.Button(frame, text = "Cancel", width = 10, command = self.cancel, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Escape>", self.cancel)
if "yes" in self.buttons:
w = tkinter.Button(frame, text = "Yes", width = 10, command = self.ok, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Return>", self.ok)
if "no" in self.buttons:
w = tkinter.Button(frame, text = "No", width = 10, command = self.cancel, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Escape>", self.cancel)
if "close" in self.buttons:
w = tkinter.Button(frame, text = "Close", width = 10, command = self.ok, default = tkinter.ACTIVE)
w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.cancel)
frame.pack()
[ドキュメント]
def update_vars(self):
"""
概要:
ダイアログ内のウィジェットの現在の値を取得し、self.retに格納します。
"""
config = self.config
tkvars = self.tkvars
widget_idx = {}
# configは既にself.configとしてアクセス可能なので、再代入は不要
# config = self.config
# tkvarsは既にself.tkvarsとしてアクセス可能なので、再代入は不要
# tkvars = self.tkvars
widget_idx = {}
wtypes = {}
for i in range(len(config)):
c = config[i]
if c.widget not in ['tab', 'frame', 'pack_frame', 'label', 'message', 'dummy', 'reset_tab', 'button']: # 値を持たないウィジェットを除外
wtypes[c.var] = c.widget
widget_idx[c.var] = i
self.ret = {}
if self.is_print:
print("")
print("Return keys in tkCustomDialog.apply():")
for key in tkvars.__dict__.keys():
if re.match(r'_', key):
continue
var = tkvars.get(key)
widget_type = wtypes.get(key, None) # キーが存在しない場合も考慮
if widget_type == 'checkbox':
val = var.get()
if val is True:
val = 1
else:
val = 0
elif widget_type == 'radiobutton':
idx_button = var.get()
if idx_button is not None:
widx = widget_idx[key]
val = config[widx].vars[idx_button]
else:
val = idx_button
elif widget_type == 'text':
# val = var.get(0., tkinter.END)
val = var.get(1., tkinter.END)
val = val.strip() # tkinter.Endで取得すると必ず改行が付くので削除
elif widget_type == 'listbox': # tkScrolledListboxの場合
sel_idx = var.curselection()
if not sel_idx: # 空のタプルの場合も考慮
val = ''
elif type(sel_idx) is tuple:
# curselection()は選択された項目のインデックスのタプルを返す
if len(sel_idx) >= 1:
# 複数選択の場合、最初の項目を取得するが、実装は単一選択を想定している可能性もある
val = var.get(sel_idx[0]) # タプルの最初の要素
else:
val = ''
else: # その他のケース(ただし、listboxはcurselection()の戻り値がタプルであることが一般的)
val = ''
elif widget_type == 'combobox': # comboboxはget()で値を取得
val = var.get()
else:
try:
val = var.get()
except AttributeError: # .get()メソッドがない場合の例外処理
print("")
print( "Warning in tktkinter.tkCustomDialog.apply():")
print(f" Can't get value for var_name={key} widget_type={widget_type}")
print(f" var={var} (Does it have a .get() method?)")
val = var # .get()がない場合は変数自体を返す
self.ret[key] = val
if self.is_print:
print(f" {key}: {val}")
[ドキュメント]
def apply(self): # ダイアログボックスを閉じた後
"""
概要:
ダイアログが閉じられた後に、ウィジェットの現在の値をself.retに格納します。
"""
self.update_vars()
[ドキュメント]
class tkSelectDialog(tkinter.simpledialog.Dialog):
"""
概要:
選択肢のリストから項目を選択させるカスタムダイアログです。
詳細説明:
メッセージとスクロール可能なリストボックスを表示し、
ユーザーがリストから1つまたは複数の項目を選択できるようにします。
"""
def __init__(self, master, app = None, title = None,
varname = None, message = "", options = [],
width = 30, height = 5,
def_index = None, is_print = False):
"""
概要:
tkSelectDialogを初期化します。
引数:
:param master: ダイアログの親ウィジェット。
:type master: tkinter.Widget
:param app: (オプション) アプリケーションのインスタンス。
:type app: object or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param varname: (オプション) 現在使用されていません。
:type varname: str or None
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str
:param options: (オプション) リストボックスに表示する選択肢のリスト。
:type options: list
:param width: (オプション) リストボックスの幅。デフォルトは30。
:type width: int
:param height: (オプション) リストボックスの高さ。デフォルトは5。
:type height: int
:param def_index: (オプション) デフォルトで選択される項目のインデックス。
:type def_index: int or None
:param is_print: (オプション) デバッグ情報を出力するかどうか。デフォルトはFalse。
:type is_print: bool
"""
self.app = app
self.ret = None
self.width = width
self.height = height
self.message = message
self.options = options
self.def_index = def_index
self.is_print = is_print
super().__init__(parent = master, title = title)
[ドキュメント]
def body(self, master) -> None:
"""
概要:
ダイアログの本体部分にメッセージラベルとスクロールリストボックスを生成します。
引数:
:param master: 親ウィジェット。
:type master: tkinter.Widget
"""
# print("def=", self.def_val)
label = tkinter.Label(master, text = self.message)
label.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
self.lbvar = tkinter.StringVar(value = self.options)
lb = tkScrolledListbox(master, listvariable = self.lbvar, width = self.width, height = self.height)
# lb = tkinter.Listbox(master, listvariable = self.lbvar, width = self.width, height = self.height)
self.listbox = lb
lb.frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
if self.def_index is not None:
self.listbox.selection_set(self.def_index) # lbをself.listboxに修正
self.listbox.see(self.def_index) # lbをself.listboxに修正
[ドキュメント]
def apply(self): # ダイアログボックスを閉じた後
"""
概要:
ダイアログが閉じられた後に、選択されたリストボックスの項目をself.retに格納します。
"""
sel_idx = self.listbox.curselection()
if not sel_idx: # sel_idxが空のタプルの場合も考慮
self.ret = None # 何も選択されていない場合はNoneを設定
return
self.ret = self.listbox.get(sel_idx[0]) # curselectionはタプルを返すため、最初の要素を取得
if self.is_print:
print("tkSelectDialog:")
print(" selected:", self.ret)
[ドキュメント]
class tkInputDialog(tkinter.simpledialog.Dialog):
"""
概要:
テキスト入力を求めるカスタムダイアログです。
詳細説明:
メッセージと複数行入力可能なテキストエリアを表示し、ユーザーにテキストを入力させます。
"""
def __init__(self, master, app = None, title = None, message = '',
width = 30, height = 10, def_val = '', is_print = False) -> None:
"""
概要:
tkInputDialogを初期化します。
引数:
:param master: ダイアログの親ウィジェット。
:type master: tkinter.Widget
:param app: (オプション) アプリケーションのインスタンス。
:type app: object or None
:param title: (オプション) ダイアログのタイトル。
:type title: str or None
:param message: (オプション) ダイアログに表示するメッセージ。
:type message: str
:param width: (オプション) テキストエリアの幅。デフォルトは30。
:type width: int
:param height: (オプション) テキストエリアの高さ。デフォルトは10。
:type height: int
:param def_val: (オプション) テキストエリアの初期値。デフォルトは空文字列。
:type def_val: str
:param is_print: (オプション) デバッグ情報を出力するかどうか。デフォルトはFalse。
:type is_print: bool
"""
self.app = app
self.ret = None
self.width = width
self.height = height
self.message = message
self.def_val = def_val
self.is_print = is_print
super().__init__(parent = master, title = title)
[ドキュメント]
def body(self, master) -> None:
"""
概要:
ダイアログの本体部分にメッセージラベルとテキストエリアを生成します。
引数:
:param master: 親ウィジェット。
:type master: tkinter.Widget
"""
# print("def=", self.def_val)
label = tkinter.Label(master, text = self.message)
label.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
# self.textvariable = tkinter.StringVar(value = self.def_val)
# text = tkinter.Entry(master, textvariable = self.textvariable, width = self.entry_width)
text = tkinter.Text(master, height = self.height, width = self.width)
text.insert(0., self.def_val)
text.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
self.text = text
[ドキュメント]
def apply(self): # ダイアログボックスを閉じた後
"""
概要:
ダイアログが閉じられた後に、テキストエリアの入力内容をself.retに格納します。
"""
# text = self.textvariable.get()
text = self.text.get(0., tkinter.END)
self.ret = text
if self.is_print:
print("Input dialog:")
print(" input_dialog_text:", text)