如何在Beeware/Toga中全局捕获键盘与鼠标事件?
在Toga框架中实现数独应用的全局事件捕获与UI方案
一、全局按键事件捕获(应用层面)
Toga的App类内置全局按键事件钩子,直接重写on_key_down/on_key_up方法即可捕获所有按键操作,无需绑定到单个控件:
核心实现代码示例
from toga import App, MainWindow, Grid, Label from toga.style import Pack class SudokuApp(App): def startup(self): self.main_window = MainWindow(title="Sudoku") self.grid = Grid() self.cells = [] self.selected_cell = None # 记录当前选中单元格的坐标(row, col) # 初始化9x9数独网格 for row in range(9): row_cells = [] for col in range(9): cell = Label( text="", style=Pack( width=60, height=60, text_align="center", font_size=18, background_color="white", border_width=1, border_color="#ccc" ) ) cell.on_click = self.handle_cell_click row_cells.append(cell) self.grid.add(cell, row=row, column=col) self.cells.append(row_cells) self.main_window.content = self.grid self.main_window.show() def on_key_down(self, widget, event): # 1. 方向键切换选中单元格 if self.selected_cell: row, col = self.selected_cell if event.key == "ArrowUp" and row > 0: self.update_selected_cell(row-1, col) elif event.key == "ArrowDown" and row < 8: self.update_selected_cell(row+1, col) elif event.key == "ArrowLeft" and col > 0: self.update_selected_cell(row, col-1) elif event.key == "ArrowRight" and col < 8: self.update_selected_cell(row, col+1) # 2. 数字键填入数值 if event.key in [str(i) for i in range(1, 10)]: if self.selected_cell: row, col = self.selected_cell self.cells[row][col].text = event.key # 此处添加清除候选数的逻辑 # 3. Alt+数字移除候选数 if event.alt and event.key in [str(i) for i in range(1, 10)]: if self.selected_cell: row, col = self.selected_cell # 此处添加移除对应候选数的逻辑 def handle_cell_click(self, widget, event): # 定位点击的单元格坐标 for row_idx, row in enumerate(self.cells): if widget in row: col_idx = row.index(widget) self.update_selected_cell(row_idx, col_idx) break def update_selected_cell(self, row, col): # 取消上一个选中单元格的样式 if self.selected_cell: prev_row, prev_col = self.selected_cell self.cells[prev_row][prev_col].style.background_color = "white" # 设置当前选中单元格的高亮样式 self.cells[row][col].style.background_color = "lightblue" self.selected_cell = (row, col) if __name__ == "__main__": SudokuApp().main_loop()
二、全局鼠标点击事件处理
- 数独场景下,最直接的方式是给每个单元格控件(如
Label)绑定on_click事件,如上述代码中的handle_cell_click,精准处理单元格选中逻辑。 - 若需捕获窗口内所有鼠标点击(包括空白区域),可给主容器(如
Grid)绑定on_mouse_down事件:
注意:子控件的点击事件默认会冒泡到父容器,若子控件已处理self.grid.on_mouse_down = self.handle_global_clickon_click,需在子控件的事件处理函数中调用event.propagate(),父容器才能收到事件。
三、UI控件选型建议
针对数独的显示需求,推荐优化Label控件的使用:
- 已填单元格:直接设置大字体显示数字,通过样式区分固定数(如黑色)和用户填入数(如蓝色)。
- 未填单元格:用小字体显示候选数,通过空格或换行分隔,例如:
# 设置候选数示例:显示1、2、3三个候选数 self.cells[row][col].text = "1 2 3" self.cells[row][col].style.font_size = 12 - 避免使用
TextInput(readonly=True):默认带输入框样式,不符合数独单元格的简洁需求;Canvas绘制成本较高,不如Label灵活;Button自带点击反馈样式,需额外禁用交互效果,反而繁琐。
四、注意事项
- Toga的样式系统在不同平台可能有细微差异,Windows下测试时可调整
border_width、font_size等参数确保显示正常。 - 若需要更精细的候选数排版,可嵌套
Box布局在单元格内,用多个小Label分别显示每个候选数,实现更规整的排列。
内容的提问来源于stack exchange,提问作者gimix
相关产品推荐
相关产品推荐

