是否将 class 集成到我现有的代码中? Tkinter,python 3

Integrating class into my existing code or not? Tkinter, python 3

我有这个 GUI 应用程序,我已经沉迷了一个多星期了,我认为它比我目前获得的有限 programming/python 知识所能管理的要难一些 - 但是,我就是不停地想它,它让我发疯,我想不通。

我用 Tkinter 创建了一个 GUI,它是一个 Todo-list 应用程序。但是,应用程序中的每个任务都必须有一些与之关联的信息。

假设我们创建了一个名为“家庭作业”的任务

我想将某些属性与家庭作业相关联。

因此,除其他外,一些属性将是 “影响”,代表完成任务的影响,从 0 到 10(例如 10:我会失败class 如果我没有完成我的 家庭作业 任务。)和截止日期 - 这是不言自明的。这些只是我想要的两个属性(不过还会有更多)。

据我所知,现在是利用 classes 的好时机。

如果我理解正确,我将不得不创建一个 class Task:,然后为每个 Task 实例设置属性。

我在熟悉 classes 之前创建了 GUI,并且我想与给定任务相关联的一些属性信息已经可以在创建任务后在 GUI 中指定(没有任何功能) ,但我不知道如何使用该信息并将其连接到给定任务,以便它具有我想要的功能。

我希望能够输入一个任务,指定与该任务相关的一些属性,然后我希望能够'summon'该任务及其相关属性进行一些简单的数学计算and/or排序。 我只希望用户看到该任务本身 - calculations/sorting 将在幕后发生。

我的问题: 我应该使用 classes 来执行此操作,还是有更初学者级别的方法可以更容易地在我现有的代码中实现? 我真的非常想弄清楚如何解决这个问题,所以任何合格的指示或正确的方向或示例、解释等都会受到真正的赞赏。

这是我的代码:

from tkcalendar import *
import tkinter.messagebox # Import the messagebox module
import pickle # Module to save to .dat
import tkinter as tk

TASKS_FILE = "tasks.dat" ##Where the task strings are saved
task_list = [] ##Don't know what I am doing yet
impact_number = []  ##Don't know if this could be useful

root = tk.Tk() # ?????
root.title('SmaToDo') # Name of the program/window
# ROOT WINDOW
# Gets the requested values of the height and widht.
windowWidth = root.winfo_reqwidth()
windowHeight = root.winfo_reqheight()
 
# Gets both half the screen width/height and window width/height
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2)
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2)
 
# Positions the window in the center of the page.
root.geometry("+{}+{}".format(positionRight, positionDown))


def new_task():
    def add_task():
        global task
        global impact_window
        task = entry_task.get() # we get the task from entry_task and we get the input from the entry_task type-field with .get()
        if task != '': # If textbox inputfield is NOT empty do this:
            listbox_tasks.insert(tkinter.END, task)
            entry_task.delete(0, tkinter.END) # 
            task_window.destroy()
        else:
            tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
            task_window.destroy()
    
    task_window = tk.Toplevel(root)
    task_window.title('Add a new task')
    task_label = tk.Label(task_window, text = 'Title your task concisely:', justify='center')
    task_label.pack()
    # Entry for tasks in new window
    entry_task = tk.Entry(task_window, width=50, justify='center')
    entry_task.pack()
    # Add task button in new window
    button_add_task = tk.Button(task_window, text='Add task', width=42, command=lambda: [add_task(), impact()])
    button_add_task.pack()



def impact():
        global impact_next_button
        global impact_window
        global impact_label
        global impact_drop
        global options
        global select

        impact_window = Toplevel(root)
        impact_window.title('Impact')
        options = StringVar()
        options.trace_add
        task_label = tk.Label(impact_window, text=str(task), font='bold')
        task_label.pack()
        impact_label = tk.Label(impact_window, text = 'Specify below the impact of completing this task \n (10 being the highest possible impact)', justify='center')
        impact_label.pack()
        impact_drop = OptionMenu(impact_window, options, '0','1','2','3','4','5','6','7','8','9','10')
        impact_drop.pack()
        impact_next_button = tk.Button(impact_window, text='Next', command=connectivity)
        impact_next_button.pack(side=tkinter.RIGHT)
        def select():
            global impact_next_button
            global impact_window
            global impact_label
            global impact_drop
            global options
            impact_number.append(str(task) + options.trace_add('write', lambda *args: print(str(task)+' '+ 'impact' + ' ' + options.get())))
            options.set(options.get())
        
    

     
def connectivity():
    impact_window.destroy()
    clicked = StringVar()
    clicked.set('Select')
    global connectivity_next_button
    global connectivity_window
    global connectivity_label
    global connectivity_drop
    connectivity_window = Toplevel(root)
    connectivity_window.title('Connectivity')
    task_label = tk.Label(connectivity_window, text=str(task), font='bold')
    task_label.pack()
    connectivity_label = tk.Label(connectivity_window, text = 'Specify below the connectivity this task has to other tasks\n (e.g. tasks you can not complete unless you have completed this task)', justify='center')
    connectivity_label.pack()
    # var1 = IntVar()
    Checkbutton(connectivity_window, text="Each task from list must be 'checkable' in this window").pack() # variable=var1).pack()

    connectivity_next_button = tk.Button(connectivity_window, text='Next', command=lambda: [Proximity(), select()])
    connectivity_next_button.pack(side=tkinter.RIGHT)

def Proximity():
    connectivity_window.destroy()
    global proximity_button
    global proximity_window
    global proximity_label
    global proximity_drop
    global cal
    global proximity_output_date
    proximity_window = Toplevel(root)
    proximity_window.title('Proxmity')
    task_label = tk.Label(proximity_window, text=str(task), font='bold')
    task_label.pack()
    proximity_label = tk.Label(proximity_window, text = 'Specify a deadline for when this task must be completed', justify='center')
    proximity_label.pack()
    cal = Calendar(proximity_window, selectmode='day', year=2021, month=4, day=27)
    cal.pack()
    def get_date():
        proximity_output_date.config(text=cal.get_date())
    proximity_date_button = tk.Button(proximity_window, text='Pick date', command=get_date)
    proximity_date_button.pack()
    proximity_output_date = tk.Label(proximity_window, text='')
    proximity_output_date.pack()
    proximity_button = tk.Button(proximity_window, text='Next', command=manageability)
    proximity_button.pack(side=tkinter.RIGHT)

def manageability():
    print('Deadline:'+' '+cal.get_date())
    proximity_window.destroy()
    clicked = StringVar()
    clicked.set('Select')
    global manageability_next_button
    global manageability_window
    global manageability_label
    global manageability_drop
    manageability_window = Toplevel(root)
    manageability_window.title('Manageability')
    task_label = tk.Label(manageability_window, text=str(task), font='bold')
    task_label.pack()
    manageability_label = tk.Label(manageability_window, text = 'Specify how difficult this task is to complete \n (0 being extremely difficult and 10 being extremely easy)', justify='center')
    manageability_label.pack()
    manageability_drop = OptionMenu(manageability_window, clicked, '0','1','2','3','4','5','6','7','8','9','10')
    manageability_drop.pack()
    manageability_next_button = tk.Button(manageability_window, text='Next', command=urgency)
    manageability_next_button.pack(side=tkinter.RIGHT)

def urgency():
    pass
    



def delete_task():
    try:
        task_index = listbox_tasks.curselection()[0]
        listbox_tasks.delete(task_index)
    except:
        tkinter.messagebox.showwarning(title='Oops', message='You must select a task to delete')

def save_tasks():
    tasks = listbox_tasks.get(0, listbox_tasks.size())
    pickle.dump(tasks, open('tasks.dat', 'wb'))




# Create UI
your_tasks_label = tk.Label(root, text='THESE ARE YOUR TASKS:', font=('Roboto',10, 'bold'), justify='center')
your_tasks_label.pack()

frame_tasks = tkinter.Frame(root)
frame_tasks.pack()

scrollbar_tasks = tkinter.Scrollbar(frame_tasks)
scrollbar_tasks.pack(side=tkinter.RIGHT, fill=tkinter.Y)

listbox_tasks = tkinter.Listbox(frame_tasks, height=10, width=50, font=('Roboto',10), justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
listbox_tasks.pack()

listbox_tasks.config(yscrollcommand=scrollbar_tasks.set)
scrollbar_tasks.config(command=listbox_tasks.yview)


try:
    tasks = pickle.load(open('tasks.dat', 'rb'))
    listbox_tasks.delete(0, tkinter.END)
    for task in tasks:
        listbox_tasks.insert(tkinter.END, task)
except:
    tkinter.messagebox.showwarning(title='Phew', message='You have no tasks')

# Add task button
button_new_task = tkinter.Button(root, text='New task', width=42, command=new_task)
button_new_task.pack()


button_delete_task = tkinter.Button(root, text='Delete task', width=42, command=delete_task)
button_delete_task.pack()


button_save_tasks = tkinter.Button(root, text='Save tasks', width=42, command=save_tasks)
button_save_tasks.pack()



















root.mainloop() # Runs the program - must be at the very buttom of the code

这里的一些人已经帮助了我很多,我真的很感激。

所以我做了一些示例代码:

from tkinter import Tk, Frame, Button, Entry, Label, Canvas, Scrollbar, OptionMenu, Toplevel, StringVar
import pickle


class Task:
    def __init__(self, name, type_, importance):
        self.name = name
        self.type = type_
        self.importance = importance


try:
    with open('saved_tasks.dat', 'rb') as file:
        task_list = pickle.load(file)

except FileNotFoundError and EOFError:
    with open('saved_tasks.dat', 'w') as file:
        pass
    print('Could not locate the file or the file was empty. A new file was created.')
    task_list = []

task_types = ['Misc', 'Math', 'Science', 'Economics', 'Biology', 'Homework']


def show_tasks():
    for widget in task_frame.winfo_children():
        widget.destroy()

    for task in task_list:
        Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')


def open_add_task():
    win = Toplevel(root)
    win.focus_force()

    Label(win, text='Write task name').grid(column=0, row=0)
    Label(win, text='Choose type of task').grid(column=1, row=0)
    Label(win, text='Choose importance of task').grid(column=2, row=0)

    entry = Entry(win)
    entry.grid(column=0, row=1, sticky='ew')

    type_var = StringVar(value=task_types[0])
    OptionMenu(win, type_var, *task_types).grid(column=1, row=1, sticky='nsew')

    imp_var = StringVar(value=1)
    OptionMenu(win, imp_var, *range(1, 10+1)).grid(column=2, row=1, sticky='nsew')

    def add_task():
        task_list.append(Task(entry.get(), type_var.get(), imp_var.get()))
        show_tasks()

    Button(win, text='Add Task', command=add_task).grid(column=0, row=2, columnspan=3, sticky='nsew')


def sort_tasks():
    type_ = sort_type.get()
    importance = sort_imp.get()
    order = asc_desc_var.get()

    for widget in task_frame.winfo_children():
        widget.destroy()

    for task in task_list if order == 'Ascending' else task_list[::-1]:
        if type_ == 'All' and importance == 'Any':
            Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
        elif type_ == 'All':
            if importance == task.importance:
                Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
        elif type_ != 'All':
            if type_ == task.type and importance == 'Any':
                Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
            elif type_ == task.type and importance == task.importance:
                Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')


root = Tk()

btn_frame = Frame(root)
btn_frame.pack(fill='x')

sort_type = StringVar(value='All')
OptionMenu(btn_frame, sort_type, 'All', *task_types).grid(column=0, row=0, sticky='nsew')

sort_imp = StringVar(value='Any')
OptionMenu(btn_frame, sort_imp,'Any', *range(1, 10+1)).grid(column=1, row=0, sticky='nsew')

asc_desc_var = StringVar(value='Ascending')
OptionMenu(btn_frame, asc_desc_var, 'Ascending', 'Descending').grid(column=2, row=0, sticky='nsew')

Button(btn_frame, text='Sort', command=sort_tasks).grid(column=0, row=1, columnspan=3, sticky='nsew')

Button(btn_frame, text='Add New', command=open_add_task).grid(column=3, row=0, rowspan=2, sticky='nsew')

task_frame_main = Frame(root)
task_frame_main.pack()

task_frame = Frame()
canvas = Canvas(task_frame_main)
canvas.pack(side='left', expand=True, fill='both')
canvas.create_window((0, 0), window=task_frame, tag='task_frame', anchor='nw')
scrollbar = Scrollbar(task_frame_main, orient='vertical', command=canvas.yview)
scrollbar.pack(side='right', fill='y')
canvas.config(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.config(scrollregion=canvas.bbox('task_frame')))

show_tasks()

root.mainloop()

try:
    with open('saved_tasks.dat', 'wb') as file:
        pickle.dump(task_list, file)
        print('File saved.')

except Exception as e:
    print(f'Exception was raised:\n{e}')

这里的主要部分是 Task class,您可以看到它非常小,这就是重点,它不一定要很大。并且 task_list 可以使用 pickle 轻松序列化并且易于加载,并且它将保留那些 classes 及其给定的属性。

如果您有任何其他问题,请提出。

解释一切(或至少):

首先导入你需要的一切,我喜欢在使用 tkinter 时这样做。

然后我定义了 task_list ,它将用于存储添加的任务,这可以而且可能应该被一个文件替换,这样你就可以读取一个文件,然后将保存的任务附加到这个列表中,或者只是使用 json 或 pickle 将文件作为列表导入,这样更容易。无论如何,然后我只是创建了一个列表来存储所有类型的任务,它可以被修改,这不是必需的,但是列表可以从文件中加载,例如,如果有添加另一种类型的选项(这需要相当多的编码)最好将它们保存到文件中以保存信息。

现在关于class Task:
这个 class 没有很多属性,实际上只有三个用于存储基本内容,例如任务的 name、任务的 type 以及它的重要性。

这没什么,但要提到的是可以使用例如 pickle 模块并将其应用于 task_list(它将存储 Task 的实例)和它将序列化此对象,同时保留其属性,因此当从 pickle 加载时,仍然可以以相同的方式访问该对象。

现在转到 show_tasks() 函数。
所以这是一个通用函数,用于显示附加到 task_list a.k.a 的所有内容。所有任务。此函数将 Labels 放在下面指定的帧上,但首先它会删除该帧上的所有内容,否则它每次都会附加所有项目,这意味着它会呈指数增长(您可以通过删除第一个来测试您是否想要循环并添加一些任务)。

接下来是open_add_task()函数,它只是用来添加任务。
它首先创建另一个 window Toplevel 并将焦点放在它上面。 (有时我喜欢 bind 这样的 windows 这样当用户点击 window 并且这个 window 失去焦点时关闭它这样就不会有多个这样的 windows 闲逛,但这取决于你)。

然后我创建了一些标签来帮助用户指出他们的要求。

然后我放置了条目,以便用户可以输入任务名称,然后为了避免用户输入错误,我让他们可以选择他们想要的类别和重要性,这样他们就不会输入错误。

然后我定义了一个函数 (add_task()) 用于从用户输入中收集数据,创建 class 实例并将其附加到 task_list。最后创建一个调用此函数的按钮。

sort_tasks 这个函数是 linked with widgets on root 所以有参考。所以首先获取用户输入的内容并将其存储在变量中以便于重用。再次通过提供选择使用户输入变得更容易。再次清除所有小部件,然后是 sorting/logical 部分,这可能可以改进,我没有添加按重要性数字排序的选项(所以它有多高)所以它按添加时间排序(再次有没有确切的时间,只是列表中的顺序是添加的时间)。逻辑可以改进,但它主要做了它应该做的事情(已经晚了,我无法正确思考)但是是的,没有太多要解释的,你自己一步一步来。

下一位是主要的 window,它不在任何函数内,因此不使用 global。首先,我启动了 Tk(),这是基本的,最后使用了 .mainloop() - 基本的东西。然后有很多框架用于帮助组织小部件,然后添加所有小部件,在这种情况下,我说的是有助于 sort_tasks() 功能的小部件。所以添加了所有的选择菜单和执行按钮sort_tasks。还有一个调用 open_add_task().

的按钮

下一位是显示所有任务的主框架。 canvas 很重要,因为 Frame 本身不能滚动,而 canvas 可以滚动,所以本质上发生的事情是将帧添加到 canvas并且 scrollregion 设置为 Frame 大小。然后添加滚动条(将添加 link 到此)。

差不多就是这些了。

编辑: 添加了保存和读取文件的功能,以便可以保存和加载任务。由于某种原因,文件必须在 Task 之后加载,因为显然否则它会抛出一个我不完全理解的错误,但我通过将 class 移到整个事情之上来处理它。我还发现了一个问题,当添加新任务并且它们超过可见帧限制时,滚动条不起作用,但是再次打开文件它工作正常,所以可以处理。正如您所看到的,我在 mainloop() 之后添加了保存功能,这样每当用户关闭 window 时,它就会将任务保存到文件中。 (添加到来源)

来源: