回答編集履歴

2

不具合修正: 1文字目の入力で数値以外が入力できた。(_extra_begin_cell_edit_func の戻り値)

2024/12/05 04:06

投稿

teamikl
teamikl

スコア8791

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
- def __init__(self, root):
74
+ logger = logging.getLogger(__name__)
76
- self.root = root
75
+ # キーイベントのログは、大量の入力が有った際、
76
+ # 他の機能のデバッグをする際に邪魔になるので、個別のロガーを準備する。
77
- self.root.title("編集サンプル")
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
- self.clear_on_first_input = True
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
- # tksheetを配置
89
+ # TODO: 負の数の入力
90
+ # TODO: 小数点の入力
92
- self.sheet.grid(row=0, column=0, columnspan=2)
91
+ return text.isdigit()
93
92
 
94
- # キー入力時のイベントバインド
95
- self.sheet.bind("<KeyPress>", self.on_key_press)
96
93
 
97
- def on_key_press(self, event):
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
- selected_cells = list(self.sheet.get_selected_cells())
101
+ table = event.widget.parent.parent
101
- if not selected_cells:
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
- if event.keysym in ("BackSpace", "Delete"):
108
+ # XXX: ここではどのカラムを編集中かを判別できない
108
- # BackspaceキーとDeleteキーの処理
109
+ if not _is_digit(event.char):
109
- new_value = current_value[:-1] if event.keysym == "BackSpace" else ""
110
- self.sheet.set_cell_data(row, col, new_value)
110
+ return "break"
111
111
 
112
- elif event.keysym in ("Left", "Right"):
112
+
113
- # 左右矢印キーの処理
114
- if event.keysym == "Left" and col > 0:
115
- self.sheet.select_cell(row, col - 1)
113
+ def _extra_begin_edit_cell_func(table, event):
116
- elif event.keysym == "Right" and col < self.sheet.total_columns - 1:
114
+ # 入力開始時に呼ばれる
117
- self.sheet.select_cell(row, col + 1)
115
+ # event オブジェクトないに編集中のColumnの情報等あり
118
116
 
119
- elif event.keysym in ("Up", "Down"):
117
+ keyLogger.debug(f"_extra_begin_edit_cell_func: {event=}")
120
- # 上下矢印キーの処理
121
- if event.keysym == "Up" and row > 0:
118
+ # XXX: isdigit のチェックが別関数で2度発生 コードの保守性へ影響
122
- self.sheet.select_cell(row - 1, col)
123
- elif event.keysym == "Down" and row < self.sheet.total_rows - 1:
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
- if __name__ == "__main__":
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
- app = SampleApp(root)
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

リアルタイムでの数値入力制限の実装例

2024/12/05 03:24

投稿

teamikl
teamikl

スコア8791

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
+