Pysimplegui:获取没有密钥的事件源

Pysimplegui: get event source without a key

提问人:Oded Sayar 提问时间:11/10/2023 最后编辑:Jason YangOded Sayar 更新时间:11/11/2023 访问量:25

问:

我正在为我的工作场所制作一个日历应用程序。我希望能够分配班次(在经理模式下)或请求和阻止班次(在工作人员模式下)。

为了方便后半部分,我希望每个班次的 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()
Python 事件处理 pysimplegui

评论


答:

1赞 Jason Yang 11/11/2023 #1

您可以为 option 设置元组值,也可以为所有 Text 元素设置元组值。keyenable_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()

enter image description here

另一种方法是使用 and 来查找触发事件的元素,但您仍然需要为这些元素分配键。window.AllKeysDictwindow.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-':