質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.34%
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

189閲覧

python-tkinterのtksheetで入力文字を数字に限定する方法

akiteru

総合スコア21

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

1クリップ

投稿2024/11/21 08:27

実現したいこと

python-tkinterのtksheetモジュールを使い、エクセル風のデータ入出力をしたい。セルの入力可能文字種は小文字数字のみ限定。セルをシングルクリックで選択時は既存データに入力値を追加及びdeleteとbackspaceキーは有効とし何れでも既存データを削除。ダブルクリックで選択時は既存データ末端にカーソル表示。左右矢印キーで移動しカーソル位置に入力数字入力及びdeleteとbackspaceキーは有効としカーソル前後文字を削除。表示文字列の先頭又は末尾で左右矢印キーが押された場合はそれぞれの方向の次のセルに移動(左端又は右端でのさらなる移動は実行しない)。全工程で上下矢印を有効とし、選択セルを上下移動させる。この様な表形式を実現したい

発生している問題・分からないこと

シングルクリックでセル選択時は数字限定が出来ました。
(1)ダブルクリックでセル選択すると数字限定が出来ません。
(2)左右矢印キーで既存表示のデータの先端又は末端で再入力時は隣のセルに移動方法が判りません

該当のソースコード

# xxx.py import tkinter as tk from tkinter import ttk from tksheet import Sheet class SampleApp: def __init__(self, root): self.root = root self.root.title("編集サンプル") # スタイル設定 self.style = ttk.Style() self.style.theme_use('alt') # 初回入力時にセル内容をクリアするフラグ self.clear_on_first_input = True # tksheetのセットアップ self.sheet = Sheet(root, data=[[f"データ {i}-{j}" for j in range(5)] for i in range(5)], headers=["A", "B", "C", "D", "E"]) self.sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell")) # tksheetを配置 self.sheet.grid(row=0, column=0, columnspan=2) # キー入力時のイベントバインド self.sheet.bind("<KeyPress>", self.on_key_press) def on_key_press(self, event): """キー入力時の処理""" # 現在のセルの位置を取得 selected_cells = list(self.sheet.get_selected_cells()) if not selected_cells: return row, col = selected_cells[0] # 現在のセル内容を取得 current_value = self.sheet.get_cell_data(row, col) if event.keysym in ("BackSpace", "Delete"): # BackspaceキーとDeleteキーの処理 new_value = current_value[:-1] if event.keysym == "BackSpace" else "" self.sheet.set_cell_data(row, col, new_value) elif event.keysym in ("Left", "Right"): # 左右矢印キーの処理 if event.keysym == "Left" and col > 0: self.sheet.select_cell(row, col - 1) elif event.keysym == "Right" and col < self.sheet.total_columns - 1: self.sheet.select_cell(row, col + 1) elif event.keysym in ("Up", "Down"): # 上下矢印キーの処理 if event.keysym == "Up" and row > 0: self.sheet.select_cell(row - 1, col) elif event.keysym == "Down" and row < self.sheet.total_rows - 1: self.sheet.select_cell(row + 1, col) elif event.char.isdigit(): # 数字キーのみ許可 new_value = (current_value or "") + event.char self.sheet.set_cell_data(row, col, new_value) else: # その他のキーは無視 pass if __name__ == "__main__": root = tk.Tk() app = SampleApp(root) root.mainloop()

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

self.sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell"))を
self.sheet.enable_bindings(("all"))にしてみたが変わらなかった

補足

特になし

teamikl👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

(1)ダブルクリックでセル選択すると数字限定が出来ません。
(2)左右矢印キーで既存表示のデータの先端又は末端で再入力時は隣のセルに移動方法が判りません

解決策にはならないかもしれませんが、問題の原因には心が当たりがあるので

  • tksheet ライブラリの実装は、Canvasを用いてます。
    <KeyPress> イベントを受け取るのは Canvas ウィジェットです。

  • 「ダブルクリックでのセルの編集」は、Canvas 内の セル自体がフォーカスを持っているわけではなく
    テキスト入力のウィジェットをセルの座標の上に配置し、セルのインライン表示に見せかけているだけです。

※ セルの数だけテキスト入力ウィジェットを作成すると、動作が重くなるので
大規模なデータを扱うテーブルは、他のライブラリでも大抵このような実装になってます。

残念ながら、Sheet クラスの__init__ のタイミングでは、
セル入力の TextEditor ウィジェットは生成されていないので、
ここで直接 TextEditor の KeyPress イベントに bind することはできません。

bind_all で子ウィジェットでもイベントを補足できますが、
他の挙動がおかしくなるので、根本的な解決にはならないかもしれません。
他の修正も必要になってきます。


(2) の矢印キーでの移動は正常に動いてるように思いますが、
ダブルクリック時には、テキストカーソルを矢印キーで動かす必要がある為、
セルの移動を矢印キーにしようとすると操作が競合してしまいます。

矢印キーによる選択セルの移動は、tksheet に既に実装されているので
再実装せずに、目的をセルへの入力制限だけに絞ると良いと思います。
(KeyPressへ bind する際でも、イベントを乗っ取り機能を再実装ということはせずに、
bind の第3引数を用いることで、イベントハンドラの「追加」が可能です)

意図してる挙動とは少し異なるかもしれませんが、(リアルタイムな数字入力の拒否ではない)
edit_validation により Callback 関数を登録できるので、
Column を判別して、数値のみなら変更をセルに反映するといったことが可能です。

python

1import tkinter as tk 2from tksheet import Sheet 3 4def edit_validation(event): 5 if event.column in (0, 2): # Column A, C を対象 6 try: 7 int(event.value) 8 except ValueError: 9 pass 10 else: 11 # 入力が許容できる場合は文字列を返す 12 return event.value 13 # None を返す場合、変更はセルへ反映されない 14 15root = tk.Tk() 16sheet = Sheet(root) 17sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell")) 18sheet.set_sheet_data([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 19sheet.edit_validation(edit_validation) 20sheet.pack(expand=True, fill=tk.BOTH) 21root.mainloop()

テキスト編集での数値以外の入力排除(テキスト編集操作のキーは許可)

python

1import tkinter as tk 2from tksheet import Sheet 3import logging 4from functools import partial 5 6logger = logging.getLogger(__name__) 7# キーイベントのログは、大量の入力が有った際、 8# 他の機能のデバッグをする際に邪魔になるので、個別のロガーを準備する。 9keyLogger = logging.getLogger(f"{__name__}.KeyEvent") 10 11 12# 実装中の機能を有効にする 13_WIP_BIND_INTERNAL_KEYPRESS = True 14# キーイベントのログを無視する 15_IGNORE_KEYEVENT_LOG = False 16# テキスト編集で、数値以外に許可するキー入力 17_INTERNAL_KEYPRESS_ALLOWKEYS = {"Left", "Right", "Home", "End", "BackSpace", "Delete"} 18 19 20def _is_digit(text): 21 # TODO: 負の数の入力 22 # TODO: 小数点の入力 23 return text.isdigit() 24 25 26def _on_keypress(event): 27 keyLogger.debug(f"_on_keypress: {event=}") 28 29 # テキスト編集中のカーソル移動・削除 30 #if event.keysym in _INTERNAL_KEYPRESS_ALLOWKEYS: 31 # return 32 33 table = event.widget.parent.parent 34 match val := event.keysym: 35 case _ if val in _INTERNAL_KEYPRESS_ALLOWKEYS: return 36 case "Tab": 37 keyLogger.debug("TAB") 38 return 39 40 # XXX: ここではどのカラムを編集中かを判別できない 41 if not _is_digit(event.char): 42 return "break" 43 44 45def _extra_begin_edit_cell_func(table, event): 46 # 入力開始時に呼ばれる 47 # event オブジェクトないに編集中のColumnの情報等あり 48 49 keyLogger.debug(f"_extra_begin_edit_cell_func: {event=}") 50 # XXX: isdigit のチェックが別関数で2度発生 ⇒ コードの保守性へ影響 51 return event.key if _is_digit(event.key) else table.get_cell_data(event.row, event.column) 52 53 54def _extra_end_edit_cell_func(table, event): 55 # NOTE: 特定の Colum のみ通知入力にしたい場合は、 56 # KeyPress を begin~で bind し、end~で unbind する。 57 keyLogger.debug(f"_extra_end_edit_cell_func: {event=}") 58 59 60if _WIP_BIND_INTERNAL_KEYPRESS: 61 # 内部の文字入力ウィジェットにアクセスする例 62 def _wip_bind_internal_keypress(root, sheet): 63 # TextEditor は、Sheet インスタンス生成時にはまだ生成されていない為、 64 # 強制的にセル選択状態にし、 65 sheet.MT.open_text_editor() 66 root.update() # tkのイベントループを進め、実際にTextEditorを生成。 67 68 # テキスト入力ウィジェット (tkinter.Text) への bind 69 # XXX: <KeyPress> へのbindだけでは、最初の1文字目がすり抜けてしまう 70 sheet.MT.text_editor.window.tktext.bind("<KeyPress>", _on_keypress) 71 72 # 上不具合の解消: 1文字目の処理 73 sheet.MT.extra_begin_edit_cell_func = partial(_extra_begin_edit_cell_func, sheet.MT) 74 # sheet.MT.extra_end_edit_cell_func = partial(_extra_end_edit_cell_func, sheet.MT) 75 76 77def main(): 78 root = tk.Tk() 79 sheet = Sheet(root) 80 sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell")) 81 sheet.set_sheet_data([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 82 sheet.pack(expand=True, fill=tk.BOTH) 83 84 if _WIP_BIND_INTERNAL_KEYPRESS: 85 _wip_bind_internal_keypress(root, sheet) 86 87 logger.info("MainLoop 開始") 88 root.mainloop() 89 90 91def _logging_config(): 92 # logging設定 93 # ⇒ コードの規模が大きくなってきたら、loggingモジュールの文書を参考に、外部設定ファイルへ 94 95 if __debug__: 96 logging.basicConfig(level=logging.DEBUG) 97 98 if _IGNORE_KEYEVENT_LOG: # Keyイベントログ表示の無効化 99 keyLogger.propagate = False 100 keyLogger.addHandler(logging.NullHandler()) 101 102 103if __name__ == '__main__': 104 _logging_config() 105 main() 106

投稿2024/12/02 06:42

編集2024/12/05 04:06
teamikl

総合スコア8791

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

akiteru

2024/12/04 04:47

ネットでtksheetを見つけ、エクセル風のことが簡単に出来ると思い、初心者にかかわらず始めましたが、日本語情報が少なく難儀していました。皆さんそれぞれに工夫され使われていることが判りました。大いに参考にさせていただきます。
teamikl

2024/12/05 03:50

参考までに、実際に試してみたので 元の質問でされていたアプローチ(リアルタイムでの数値入力制限)を回答に実装例を追記しました。 (ダブルクリックでなくても、セル選択時に直接数値入力でTextEditorでの入力になります) 個人的な経験から言うと、この方法での実装(ライブラリ側の想定していない、内部構造に手を入れる)は、 あまりお勧めはしません。勿論、状況や用途次第ですがコードの保守性とのトレードオフになります。 ある機能を実装する為に内部に手を入れると、他の操作で不具合が発生するみたいなことが頻発し、 問題解決の為には、ソースコード内部まで読むことになります。 また、ライブラリ側の変更があると、挙動が変わったりする可能性もあります。 ⇒ 公開メソッドはある程度互換性に配慮があるが、  内部に関しては更新履歴に記載なく変更されることがある。例: 属性名の変更等。
akiteru

2024/12/05 08:01

さらなるご指摘ありがとうございます。初見ではコードを十分理解できないレベルです。各コマンドを個々に理解し解明します。当方は何か別の方法を工夫して実現できないか検討してみることにしています。ありがとうございました
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.34%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問