提问人:Oded Sayar 提问时间:11/10/2023 最后编辑:Jason YangOded Sayar 更新时间:11/11/2023 访问量:25
Pysimplegui:获取没有密钥的事件源
Pysimplegui: get event source without a key
问:
我正在为我的工作场所制作一个日历应用程序。我希望能够分配班次(在经理模式下)或请求和阻止班次(在工作人员模式下)。
为了方便后半部分,我希望每个班次的 Text 元素都是可点击的,例如,将其设置为红色表示块。
我的问题是,我知道创建这么多可识别的可点击元素的唯一方法是为每个元素分配不同的键,并且每个月都有很多移位元素( >120)。
有没有其他方法可以知道哪个元素引发了事件?我还有什么遗漏的吗?我觉得必须有一种比上面提到的更优雅、更易于维护的方式。
我将粘贴用于生成日历的代码,以防万一。
from datetime import datetime
import calendar
import json
import PySimpleGUI as sg
sg.theme('DarkGrey15') # Add a touch of color
min_weeks, max_weeks = (4, 6)
'Dec']
weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
workers = ['worker1', 'worker2', 'worker3', 'worker4', 'worker5']
shift_emoji = {'full': '🌈', 'day': '☀️', 'night': '🌙'}
selector_key = 'MonthSelectorKey'
browser_key = 'JsonBrowserKey'
worker_mode_button_title = ['create request file', 'back to shift assignment']
worker_mode_key = 'WorkerModeKey'
worker_requests = ['block', 'request', 'cancel']
worker_requests_default = worker_requests[0]
worker_requests_key = 'WorkerRequestsKey'
exoprt_key = 'ExportKey'
class StateClass:
def __init__(self, month_start = datetime.now().replace(day= 1)) -> None:
self.blocks = {}
self.month_start = month_start
if month_start.month < 12:
self.month_start = month_start.replace(month= month_start.month+1)
else:
self.month_start = month_start.replace(year= month_start.year+1, month= 1)
self.month_range = calendar.monthrange(month_start.year, month_start.month)
self.worker_mode = False
state = StateClass()
def CreateShiftUi(text: str):
return [sg.Text(text, expand_x=True, enable_events=True), sg.Combo(workers, enable_events=True)]
def CreateWorkdayUi():
return [CreateShiftUi(emo) for emo in shift_emoji.values()]
def CreateWorkdayFrame(day: int):
return sg.Frame(day, CreateWorkdayUi())
def CreateWeekRow(start_day: int):
return [CreateWorkdayFrame(start_day+i) for i in range(7)]
def WorkdaySetEnable(workday_frame: sg.Frame, f_enable: bool):
for row in workday_frame.Rows:
row[-1].update('', values= workers, disabled= not f_enable)
def WeekSetEnable(week: list[sg.Frame], f_enable: bool, until: int=6):
for workday in week[:until+1]:
WorkdaySetEnable(workday, f_enable)
for workday in week[until+1:]:
WorkdaySetEnable(workday, not f_enable)
def WeekSetHidden(week_row: list[sg.Frame], f_hide: bool):
if week_row[0].visible != f_hide:
return
for workday in week_row:
workday.update(visible= not f_hide)
def OnMonthChanged():
new_year = month_row[0].get()
new_month = month_row[1].get()
if new_month == state.month_start.month and new_year == state.month_start.year:
return
state.month_start = state.month_start.replace(year= new_year, month= new_month)
ResetCalendar()
def CalendarFrame(day_in_month):
day = day_in_month + state.month_range[0]
return calendar_layout[day//7][day%7]
def ResetCalendar():
global calendar_layout
state.month_range = calendar.monthrange(state.month_start.year, state.month_start.month)
start_weekday = (state.month_range[0]+1) % 7
weeks_in_month = (state.month_range[1]+start_weekday+6) // 7
for week in calendar_layout[min_weeks : weeks_in_month]:
WeekSetHidden(week, False)
for week in calendar_layout[weeks_in_month : max_weeks]:
WeekSetHidden(week, True)
WeekSetEnable(week, False)
# Disable out-of-month days
WeekSetEnable(calendar_layout[0], False, start_weekday-1)
for week in calendar_layout[1 : weeks_in_month-1]:
WeekSetEnable(week, True)
end_weekday = (start_weekday + state.month_range[1] - 1) % 7
WeekSetEnable(calendar_layout[weeks_in_month-1], True, end_weekday)
# Renumber days in month
for i in range(1, 1+state.month_range[1]):
CalendarFrame(i).update(i)
prev_month = (state.month_start.month-2)%12 + 1
prev_month_len = calendar.monthrange(state.month_start.year - int(prev_month == 12), prev_month)[1]
for i in range(start_weekday):
calendar_layout[0][i].update(prev_month_len - start_weekday + i + 1)
for i in range(end_weekday+1,7):
calendar_layout[weeks_in_month-1][i].update(i-end_weekday)
def ApplyBlockfile(block_file):
# Read the Json file
with open(block_file) as json_file:
block_data = json.load(json_file)
# Validate worker's name
name = block_data['worker_name'].capitalize()
if not name in workers:
print(name + " doesn't appear on you list")
return
# Remove from relevant Combos
for blocking in block_data['blockings'].items():
blocked_day = int(blocking[0])
blocked_frame = CalendarFrame(blocked_day)
for blocked_shift in blocking[1]:
shift_type = list(shift_emoji).index(blocked_shift)
shift_combo: sg.Combo = blocked_frame.Rows[shift_type][-1]
new_list = shift_combo.Values.copy()
if name in new_list:
new_list.remove(name)
shift_combo.update(values= new_list)
def OnWorkerMode():
new_mode = not state.worker_mode
state.worker_mode = new_mode
window[worker_mode_key].update(worker_mode_button_title[new_mode])
window[worker_requests_key].update(visible= new_mode)
if new_mode:
pass
else:
window[worker_requests_key].update(worker_requests_default)
calendar_layout = [CreateWeekRow(1+7*j) for j in range(max_weeks)]
month_row = [sg.Spin([i for i in range(2020,2100)], state.month_start.year),
sg.Combo([i for i in range(1,13)],state.month_start.month, key=selector_key, enable_events=True),
sg.FilesBrowse('Browse', enable_events= True, file_types= (("JSON", "*.json"),), target= browser_key),
sg.Input(key= browser_key, visible= False, enable_events= True, expand_x= True),
sg.Button(worker_mode_button_title[0], key=worker_mode_key, enable_events= True),
sg.Combo(worker_requests, worker_requests_default, key=worker_requests_key, visible=False, enable_events=True),
sg.Button('export', key= exoprt_key)]
weekday_titles = [sg.Text(weekdays[i],expand_x=True, justification='center') for i in range(len(weekdays))]
layout = [
month_row,
weekday_titles,
calendar_layout,
]
# Create the Window
window = sg.Window('Window Title', layout, finalize=True)
ResetCalendar()
# Event Loop to process "events" and get the "values" of the inputs
while True:
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Cancel': # if user closes window or clicks cancel
break
if event == selector_key:
OnMonthChanged()
continue
if event == browser_key:
for block_file in values[browser_key].split(';'):
ApplyBlockfile(block_file)
continue
if event == worker_mode_key:
OnWorkerMode()
continue
print('You entered ', values[0])
window.close()
答:
1赞
Jason Yang
11/11/2023
#1
您可以为 option 设置元组值,也可以为所有 Text 元素设置元组值。key
enable_events=True
演示代码
import PySimpleGUI as sg
class Text(sg.Text):
mark = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def select(self):
if Text.mark:
Text.mark.update(background_color=sg.theme_background_color())
Text.mark = self
self.update(background_color="blue")
font = ("Courier New", 16, "bold")
sg.set_options(font=font)
layout = [[Text(f"({row}, {col})", enable_events=True, key=('Text', row, col)) for col in range(7)] for row in range(6)]
window = sg.Window("Title", layout, background_color='green', finalize=True)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif isinstance(event, tuple) and event[0]=='Text':
window[event].select()
window.close()
另一种方法是使用 and 来查找触发事件的元素,但您仍然需要为这些元素分配键。window.AllKeysDict
window.user_bind_event.widget
import PySimpleGUI as sg
class Text(sg.Text):
mark = None
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def select(self):
if Text.mark:
Text.mark.update(background_color=sg.theme_background_color())
Text.mark = self
self.update(background_color="blue")
font = ("Courier New", 16, "bold")
sg.set_options(font=font)
layout = [[Text(f"({row}, {col})", enable_events=True, key=(row, col)) for col in range(7)] for row in range(6)]
window = sg.Window("Title", layout, background_color='green', finalize=True)
widget_to_element = {value.widget:value for value in window.AllKeysDict.values()}
window.bind("<Button-1>", "Click")
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == "Click":
widget = window.user_bind_event.widget
if widget in widget_to_element:
element = widget_to_element[widget]
if isinstance(element, Text):
element.select()
window.close()
评论
0赞
Oded Sayar
11/11/2023
非常感谢!这将使它更易于管理
0赞
Mike from PSG
11/11/2023
元组作为键在许多情况下非常有用。如果你有很多元素要管理,正如你所看到的,它们非常适合。如果您想将“类别”中的键分组,例如“从线程发送的事件”,或者选项卡中的布局看起来相似(使用选项卡名称作为元组的一部分),它们也很好。(“Tab1”、“-INPUT-”)。
0赞
Mike from PSG
11/11/2023
您还可以使用安全访问元组事件,而无需测试它是否是元组。例如,如果线程发送类似 的事件,则可以检查语句是否发生了线程事件event[0]
("-FROM THREAD-", text_to_output)
if event[0] == '-FROM THREAD-':
评论