回答編集履歴
2
不具合修正: 1文字目の入力で数値以外が入力できた。(_extra_begin_cell_edit_func の戻り値)
test
CHANGED
@@ -66,75 +66,112 @@
|
|
66
66
|
## テキスト編集での数値以外の入力排除(テキスト編集操作のキーは許可)
|
67
67
|
|
68
68
|
```python
|
69
|
-
# xxx.py
|
70
69
|
import tkinter as tk
|
71
|
-
from tkinter import ttk
|
72
70
|
from tksheet import Sheet
|
71
|
+
import logging
|
72
|
+
from functools import partial
|
73
73
|
|
74
|
-
class SampleApp:
|
75
|
-
|
74
|
+
logger = logging.getLogger(__name__)
|
76
|
-
|
75
|
+
# キーイベントのログは、大量の入力が有った際、
|
76
|
+
# 他の機能のデバッグをする際に邪魔になるので、個別のロガーを準備する。
|
77
|
-
|
77
|
+
keyLogger = logging.getLogger(f"{__name__}.KeyEvent")
|
78
78
|
|
79
|
-
# スタイル設定
|
80
|
-
self.style = ttk.Style()
|
81
|
-
self.style.theme_use('alt')
|
82
79
|
|
83
|
-
|
80
|
+
# 実装中の機能を有効にする
|
84
|
-
|
81
|
+
_WIP_BIND_INTERNAL_KEYPRESS = True
|
82
|
+
# キーイベントのログを無視する
|
83
|
+
_IGNORE_KEYEVENT_LOG = False
|
84
|
+
# テキスト編集で、数値以外に許可するキー入力
|
85
|
+
_INTERNAL_KEYPRESS_ALLOWKEYS = {"Left", "Right", "Home", "End", "BackSpace", "Delete"}
|
85
86
|
|
86
|
-
# tksheetのセットアップ
|
87
|
-
self.sheet = Sheet(root, data=[[f"データ {i}-{j}" for j in range(5)] for i in range(5)],
|
88
|
-
headers=["A", "B", "C", "D", "E"])
|
89
|
-
self.sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell"))
|
90
87
|
|
88
|
+
def _is_digit(text):
|
91
|
-
|
89
|
+
# TODO: 負の数の入力
|
90
|
+
# TODO: 小数点の入力
|
92
|
-
|
91
|
+
return text.isdigit()
|
93
92
|
|
94
|
-
# キー入力時のイベントバインド
|
95
|
-
self.sheet.bind("<KeyPress>", self.on_key_press)
|
96
93
|
|
97
|
-
|
94
|
+
def _on_keypress(event):
|
98
|
-
|
95
|
+
keyLogger.debug(f"_on_keypress: {event=}")
|
96
|
+
|
97
|
+
# テキスト編集中のカーソル移動・削除
|
98
|
+
#if event.keysym in _INTERNAL_KEYPRESS_ALLOWKEYS:
|
99
|
-
|
99
|
+
# return
|
100
|
+
|
100
|
-
|
101
|
+
table = event.widget.parent.parent
|
101
|
-
|
102
|
+
match val := event.keysym:
|
103
|
+
case _ if val in _INTERNAL_KEYPRESS_ALLOWKEYS: return
|
104
|
+
case "Tab":
|
105
|
+
keyLogger.debug("TAB")
|
102
106
|
return
|
103
|
-
row, col = selected_cells[0]
|
104
|
-
# 現在のセル内容を取得
|
105
|
-
current_value = self.sheet.get_cell_data(row, col)
|
106
107
|
|
107
|
-
|
108
|
+
# XXX: ここではどのカラムを編集中かを判別できない
|
108
|
-
|
109
|
+
if not _is_digit(event.char):
|
109
|
-
new_value = current_value[:-1] if event.keysym == "BackSpace" else ""
|
110
|
-
|
110
|
+
return "break"
|
111
111
|
|
112
|
-
|
112
|
+
|
113
|
-
# 左右矢印キーの処理
|
114
|
-
if event.keysym == "Left" and col > 0:
|
115
|
-
|
113
|
+
def _extra_begin_edit_cell_func(table, event):
|
116
|
-
|
114
|
+
# 入力開始時に呼ばれる
|
117
|
-
|
115
|
+
# event オブジェクトないに編集中のColumnの情報等あり
|
118
116
|
|
119
|
-
|
117
|
+
keyLogger.debug(f"_extra_begin_edit_cell_func: {event=}")
|
120
|
-
# 上下矢印キーの処理
|
121
|
-
|
118
|
+
# XXX: isdigit のチェックが別関数で2度発生 ⇒ コードの保守性へ影響
|
122
|
-
self.sheet.select_cell(row - 1, col)
|
123
|
-
|
119
|
+
return event.key if _is_digit(event.key) else table.get_cell_data(event.row, event.column)
|
124
|
-
self.sheet.select_cell(row + 1, col)
|
125
120
|
|
126
|
-
elif event.char.isdigit():
|
127
|
-
# 数字キーのみ許可
|
128
|
-
new_value = (current_value or "") + event.char
|
129
|
-
self.sheet.set_cell_data(row, col, new_value)
|
130
|
-
else:
|
131
|
-
# その他のキーは無視
|
132
|
-
pass
|
133
121
|
|
134
|
-
|
122
|
+
def _extra_end_edit_cell_func(table, event):
|
123
|
+
# NOTE: 特定の Colum のみ通知入力にしたい場合は、
|
124
|
+
# KeyPress を begin~で bind し、end~で unbind する。
|
125
|
+
keyLogger.debug(f"_extra_end_edit_cell_func: {event=}")
|
126
|
+
|
127
|
+
|
128
|
+
if _WIP_BIND_INTERNAL_KEYPRESS:
|
129
|
+
# 内部の文字入力ウィジェットにアクセスする例
|
130
|
+
def _wip_bind_internal_keypress(root, sheet):
|
131
|
+
# TextEditor は、Sheet インスタンス生成時にはまだ生成されていない為、
|
132
|
+
# 強制的にセル選択状態にし、
|
133
|
+
sheet.MT.open_text_editor()
|
134
|
+
root.update() # tkのイベントループを進め、実際にTextEditorを生成。
|
135
|
+
|
136
|
+
# テキスト入力ウィジェット (tkinter.Text) への bind
|
137
|
+
# XXX: <KeyPress> へのbindだけでは、最初の1文字目がすり抜けてしまう
|
138
|
+
sheet.MT.text_editor.window.tktext.bind("<KeyPress>", _on_keypress)
|
139
|
+
|
140
|
+
# 上不具合の解消: 1文字目の処理
|
141
|
+
sheet.MT.extra_begin_edit_cell_func = partial(_extra_begin_edit_cell_func, sheet.MT)
|
142
|
+
# sheet.MT.extra_end_edit_cell_func = partial(_extra_end_edit_cell_func, sheet.MT)
|
143
|
+
|
144
|
+
|
145
|
+
def main():
|
135
146
|
root = tk.Tk()
|
136
|
-
|
147
|
+
sheet = Sheet(root)
|
148
|
+
sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell"))
|
149
|
+
sheet.set_sheet_data([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
150
|
+
sheet.pack(expand=True, fill=tk.BOTH)
|
151
|
+
|
152
|
+
if _WIP_BIND_INTERNAL_KEYPRESS:
|
153
|
+
_wip_bind_internal_keypress(root, sheet)
|
154
|
+
|
155
|
+
logger.info("MainLoop 開始")
|
137
156
|
root.mainloop()
|
157
|
+
|
158
|
+
|
159
|
+
def _logging_config():
|
160
|
+
# logging設定
|
161
|
+
# ⇒ コードの規模が大きくなってきたら、loggingモジュールの文書を参考に、外部設定ファイルへ
|
162
|
+
|
163
|
+
if __debug__:
|
164
|
+
logging.basicConfig(level=logging.DEBUG)
|
165
|
+
|
166
|
+
if _IGNORE_KEYEVENT_LOG: # Keyイベントログ表示の無効化
|
167
|
+
keyLogger.propagate = False
|
168
|
+
keyLogger.addHandler(logging.NullHandler())
|
169
|
+
|
170
|
+
|
171
|
+
if __name__ == '__main__':
|
172
|
+
_logging_config()
|
173
|
+
main()
|
174
|
+
|
138
175
|
```
|
139
176
|
|
140
177
|
|
1
リアルタイムでの数値入力制限の実装例
test
CHANGED
@@ -61,5 +61,81 @@
|
|
61
61
|
root.mainloop()
|
62
62
|
```
|
63
63
|
|
64
|
+
----
|
65
|
+
|
66
|
+
## テキスト編集での数値以外の入力排除(テキスト編集操作のキーは許可)
|
67
|
+
|
68
|
+
```python
|
69
|
+
# xxx.py
|
70
|
+
import tkinter as tk
|
71
|
+
from tkinter import ttk
|
72
|
+
from tksheet import Sheet
|
73
|
+
|
74
|
+
class SampleApp:
|
75
|
+
def __init__(self, root):
|
76
|
+
self.root = root
|
77
|
+
self.root.title("編集サンプル")
|
78
|
+
|
79
|
+
# スタイル設定
|
80
|
+
self.style = ttk.Style()
|
81
|
+
self.style.theme_use('alt')
|
82
|
+
|
83
|
+
# 初回入力時にセル内容をクリアするフラグ
|
84
|
+
self.clear_on_first_input = True
|
85
|
+
|
86
|
+
# tksheetのセットアップ
|
87
|
+
self.sheet = Sheet(root, data=[[f"データ {i}-{j}" for j in range(5)] for i in range(5)],
|
88
|
+
headers=["A", "B", "C", "D", "E"])
|
89
|
+
self.sheet.enable_bindings(("single_select", "arrowkeys", "edit_cell"))
|
90
|
+
|
91
|
+
# tksheetを配置
|
92
|
+
self.sheet.grid(row=0, column=0, columnspan=2)
|
93
|
+
|
94
|
+
# キー入力時のイベントバインド
|
95
|
+
self.sheet.bind("<KeyPress>", self.on_key_press)
|
96
|
+
|
97
|
+
def on_key_press(self, event):
|
98
|
+
"""キー入力時の処理"""
|
99
|
+
# 現在のセルの位置を取得
|
100
|
+
selected_cells = list(self.sheet.get_selected_cells())
|
101
|
+
if not selected_cells:
|
102
|
+
return
|
103
|
+
row, col = selected_cells[0]
|
104
|
+
# 現在のセル内容を取得
|
105
|
+
current_value = self.sheet.get_cell_data(row, col)
|
106
|
+
|
107
|
+
if event.keysym in ("BackSpace", "Delete"):
|
108
|
+
# BackspaceキーとDeleteキーの処理
|
109
|
+
new_value = current_value[:-1] if event.keysym == "BackSpace" else ""
|
110
|
+
self.sheet.set_cell_data(row, col, new_value)
|
111
|
+
|
112
|
+
elif event.keysym in ("Left", "Right"):
|
113
|
+
# 左右矢印キーの処理
|
114
|
+
if event.keysym == "Left" and col > 0:
|
115
|
+
self.sheet.select_cell(row, col - 1)
|
116
|
+
elif event.keysym == "Right" and col < self.sheet.total_columns - 1:
|
117
|
+
self.sheet.select_cell(row, col + 1)
|
118
|
+
|
119
|
+
elif event.keysym in ("Up", "Down"):
|
120
|
+
# 上下矢印キーの処理
|
121
|
+
if event.keysym == "Up" and row > 0:
|
122
|
+
self.sheet.select_cell(row - 1, col)
|
123
|
+
elif event.keysym == "Down" and row < self.sheet.total_rows - 1:
|
124
|
+
self.sheet.select_cell(row + 1, col)
|
125
|
+
|
126
|
+
elif event.char.isdigit():
|
127
|
+
# 数字キーのみ許可
|
128
|
+
new_value = (current_value or "") + event.char
|
129
|
+
self.sheet.set_cell_data(row, col, new_value)
|
130
|
+
else:
|
131
|
+
# その他のキーは無視
|
132
|
+
pass
|
133
|
+
|
134
|
+
if __name__ == "__main__":
|
135
|
+
root = tk.Tk()
|
136
|
+
app = SampleApp(root)
|
137
|
+
root.mainloop()
|
138
|
+
```
|
64
139
|
|
65
140
|
|
141
|
+
|