我目前正在尝试开发一个Pomodoro应用程序,该应用程序将包含多个屏幕(Tkinter帧),例如指示下一个任务的StartWorking屏幕和一个“ Go”按钮,该按钮将同时显示倒数计时屏幕启动VLC。
倒计时结束时,将出现一个SprintEnd屏幕,以确认执行的任务是计划中的任务。
当前代码如下:
import logging #To log activity
import tkinter as tk #To manage GUI
from tkinter import ttk #For more modern displays in the GUI
from datetime import datetime, timedelta #To manage dates and times
import time #To manage sleep time
from PIL import Image as PIL_Image, ImageTk as PIL_ImageTk #To manage image conversions
import pandas as pd #To read from and write in the csv database
import os #To manage paths
import random #To choose random music from the playlist
import subprocess #To launch VLC
# Default style
TITLE_FONT = ('Verdana', 18)
SUBTITLE_FONT = ('Verdana', 7, 'italic')
LARGE_FONT = ('Verdana', 12, 'bold')
SMALL_FONT = ('Verdana', 8)
class Window(tk.Tk):
def __init__(self, *args, **kwargs):
""" Creation of the main window that will host each frame"""
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.iconbitmap(self, default='img/tomato.ico')
tk.Tk.title(self, 'My Pomodoro app')
#tk.Tk.minsize(self, 400, 400)
# Creating a main container that will display the current frame
container = tk.Frame(self)
container.grid(sticky='nsew')
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
# Listing all the frames that can be displayed in our container
self.frames = {}
for frm in (Welcome, StartWorking, Countdown, SprintEnd, Planning, Planning_tasks, Monitoring):
frame = frm(container, self)
self.frames[frm] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(Welcome)
def show_frame(self, container):
"""Function that will bring up the current frame in our container
Args:
container (tkinter Frame): an empty frame that will display another selected frame in itself above all others
"""
frame = self.frames[container]
frame.tkraise()
def start_countdown(self, duration):
# Starting soft music from the playlist within VLC
playlist = os.listdir('audio/playlist')
music = random.randint(0, len(playlist) - 1)
subprocess.Popen(['C:/Program Files/VideoLAN/VLC/vlc.exe', ''.join(['audio/playlist/', playlist[music]])])
# Countdown passed into seconds
duration = duration * 60
while duration > 0:
mins, secs = divmod(duration, 60)
timeleft_label = tk.Label(self, text='{:02d}:{:02d}'.format(mins, secs), font=LARGE_FONT)
timeleft_label.grid(row=1, column=0, pady=20, padx=10, sticky='nsew')
self.update()
self.wm_attributes('-topmost', 1)
time.sleep(1)
duration -= 1
# End of countdown
timeleft_label.destroy()
subprocess.call(['C:/Program Files/VideoLAN/VLC/vlc.exe', 'vlc://quit'], shell=False)
self.show_frame(SprintEnd)
return
class Welcome(tk.Frame):
def __init__(self, parent, controller):
""" Welcome page that will enable to choose between 3 options: planning, working or monitoring.
Args:
parent (tkinter Frame): a widget (Window) to act as the parent of the current object
controller (tkinter Frame): object that is designed to act as a common point of interaction for several pages of widgets
"""
# Main title
tk.Frame.__init__(self,parent)
title = tk.Label(self, text='Welcome to My Pomodoro', font=TITLE_FONT)
title.grid(pady=20,padx=10, sticky='nsew', row=0, column=0, columnspan=3)
# Subtitle
subtitle = tk.Label(self, text='powered by Blue Owl', font=SUBTITLE_FONT)
subtitle.grid(padx=10, pady=5, sticky='e', row=1, column=2)
# Main image
image = PIL_Image.open(r'img/tomato.png')
photo = PIL_ImageTk.PhotoImage(image)
logo = tk.Label(self, image=photo)
logo.image = photo
logo.grid(row=2, column=0, columnspan=3, sticky='nsew', pady=20)
# Navigation buttons to reach other pages
"""These buttons actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Start working', command=lambda: controller.show_frame(StartWorking))
button_navigate_working.grid(row=3, column=0, padx=10, pady=30, sticky='sew')
button_navigate_planning = ttk.Button(self, text='Planning', command=lambda: controller.show_frame(Planning))
button_navigate_planning.grid(row=3, column=1, padx=10, pady=30, sticky='sew')
button_navigate_monitoring = ttk.Button(self, text='Monitoring', command=lambda: controller.show_frame(Monitoring))
button_navigate_monitoring.grid(row=3, column=2, padx=10, pady=30, sticky='sew')
class StartWorking(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
title = tk.Label(self, text='Start Working', font=TITLE_FONT)
title.grid(pady=20,padx=10, sticky='nsew', row=0, column=0, columnspan=4)
# Reading from database
dataframe = pd.read_csv('files/db_test.csv', sep=';')
# Converting strings objects into datetime objects
dataframe['Datetime'] = pd.to_datetime(dataframe['Day'] + ' ' + dataframe['Time'], format='%d/%m/%Y %H:%M')
dataframe['Deltatime'] = datetime.now() - dataframe['Datetime']
# Comparing to current datetime and identifying next planned task
future_filter = (dataframe['Deltatime'] < timedelta(0))
dataframe_future = dataframe.loc[(future_filter)]
try:
next_task = dataframe_future.loc[dataframe_future['Deltatime'].idxmax()]['Planned task']
# Display next task and start working
label_next_task = tk.Label(self, text='Next task :', font=SMALL_FONT)
label_next_task.grid(pady=50,padx=10, sticky='nsew', row=1, column=1)
label_task = tk.Label(self, text=next_task, font=LARGE_FONT)
label_task.grid(pady=50,padx=10, sticky='nsew', row=1, column=2)
button_navigate_countdown = ttk.Button(self, text='Start task !', command=lambda: [controller.show_frame(Countdown), controller.start_countdown(25)])
button_navigate_countdown.grid(row=2, column=1, columnspan=2, padx=10, pady=30)
except ValueError:
# If there is no future task planned yet, user has to plan first
next_task = 'Not planned yet'
label_task = tk.Label(self, text='No task planned !', font=LARGE_FONT, fg='red')
label_task.grid(pady=20,padx=10, sticky='nsew', row=1, column=1, columnspan=2)
button_navigate_countdown = ttk.Button(self, text='Back to planning', command=lambda: controller.show_frame(Planning))
button_navigate_countdown.grid(row=2, column=1, columnspan=2, padx=10, pady=10)
# Navigation buttons to reach other pages
"""They actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Back to home', command=lambda: controller.show_frame(Welcome))
button_navigate_working.grid(row=3, column=0, padx=10, pady=30, sticky='sew')
button_navigate_planning = ttk.Button(self, text='Planning', command=lambda: controller.show_frame(Planning))
button_navigate_planning.grid(row=3, column=1, padx=10, pady=30, sticky='sew')
button_navigate_monitoring = ttk.Button(self, text='Monitoring', command=lambda: controller.show_frame(Monitoring))
button_navigate_monitoring.grid(row=3, column=2, padx=10, pady=30, sticky='sew')
button_navigate_monitoring = ttk.Button(self, text='End of sprint', command=lambda: controller.show_frame(SprintEnd))
button_navigate_monitoring.grid(row=3, column=3, padx=10, pady=30, sticky='sew')
class SprintEnd(tk.Frame):
def __init__(self, parent, controller):
""" Work page that will enable to validate the latest task and rate it, then to launch a new task.
Args:
parent (tkinter Frame): a widget (Window) to act as the parent of the current object
controller (tkinter Frame): object that is designed to act as a common point of interaction for several pages of widgets
"""
tk.Frame.__init__(self, parent)
# Main title
title = tk.Label(self, text='End of sprint', font=TITLE_FONT)
title.grid(pady=20,padx=10, sticky='nsew', row=0, column=0, columnspan=3)
# Loading images
validate_icon = tk.PhotoImage(file=r'img/validate.png').subsample(10, 10)
# Reading from database
dataframe = pd.read_csv('files/db_test.csv', sep=';')
# Converting strings objects into datetime objects
dataframe['Datetime'] = pd.to_datetime(dataframe['Day'] + ' ' + dataframe['Time'], format='%d/%m/%Y %H:%M')
# Comparing to current datetime and identifying most recent planned task
dataframe['Deltatime'] = datetime.now() - dataframe['Datetime']
past_filter = (dataframe['Deltatime'] >= timedelta(0))
dataframe_past = dataframe.loc[(past_filter)]
try:
latest_task = dataframe_past.loc[dataframe_past['Deltatime'].idxmin()]['Planned task']
latest_index = dataframe_past.loc[dataframe_past['Deltatime'].idxmin()]
except ValueError:
latest_task = 'Undefined'
latest_index = {
'Day' : datetime.now().strftime('%d/%m/%Y'),
'Time' : datetime.now().strftime('%H:%M'),
'Planned task' : 'Undefined',
'Personnal' : 'Private',
'Actual task' : 'Undefined',
'Efficiency' : 1
}
# Comparing to current datetime and identifying next planned task
future_filter = (dataframe['Deltatime'] < timedelta(0))
dataframe_future = dataframe.loc[(future_filter)]
try:
next_task = dataframe_future.loc[dataframe_future['Deltatime'].idxmax()]['Planned task']
except ValueError:
next_task = 'Undefined'
# Displaying latest planned task information in the frame
latest_task_label1 = tk.Label(self, text='Latest planned task', font=SMALL_FONT)
latest_task_label1.grid(pady=5, padx=10, sticky='nsew', row=1, column=1)
latest_task_label2 = tk.Label(self, text=latest_task, font=LARGE_FONT)
latest_task_label2.grid(pady=5, padx=10, sticky='nsew', row=2, column=1)
# Displaying labels about actual latest task
real_latest_task_label = tk.Label(self, text='Actual task', font=SMALL_FONT)
real_latest_task_label.grid(pady=5, padx=10, sticky='nsew', row=3, column=0)
privacy_latest_task_label = tk.Label(self, text='Private', font=SMALL_FONT)
privacy_latest_task_label.grid(pady=5, padx=10, sticky='nsew', row=3, column=1)
efficiency_latest_task_label = tk.Label(self, text='Efficiency', font=SMALL_FONT)
efficiency_latest_task_label.grid(pady=5, padx=10, sticky='nsew', row=3, column=2)
# Getting information about actual latest task
real_task = tk.StringVar()
real_latest_task = ttk.Entry(self, textvariable=real_task)
real_latest_task.grid(pady=5, padx=10, sticky='nsew', row=4, column=0)
privacy = tk.StringVar()
privacy_latest_task = tk.Checkbutton(self, variable=privacy, onvalue='Private', offvalue='Professional', justify='center')
privacy_latest_task.deselect()
privacy_latest_task.grid(pady=5, padx=10, sticky='nsew', row=4, column=1)
efficiency=tk.IntVar()
efficiency_latest_task = ttk.Entry(self, textvariable=efficiency)
efficiency_latest_task.grid(pady=5, padx=10, sticky='nsew', row=4, column=2)
next_task_label1 = tk.Label(self, text='Next planned task', font=SMALL_FONT)
next_task_label1.grid(pady=5, padx=10, sticky='nsew', row=5, column=1)
next_task_label2 = tk.Label(self, text=next_task, font=LARGE_FONT)
next_task_label2.grid(pady=5, padx=10, sticky='nsew', row=6, column=1)
validate_button = ttk.Button(self, text='Validate all', image=validate_icon, compound='left',
command=lambda: self.validate_input(controller, real_task, privacy, efficiency, latest_index, dataframe))
validate_button.grid(pady=20, padx=10, sticky='nsew', row=7, column=2)
# Navigation buttons to reach other pages
"""They actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Back to home', command=lambda: controller.show_frame(Welcome))
button_navigate_working.grid(row=9, column=0, pady=10, padx=10)
button_navigate_planning = ttk.Button(self, text='Planning', command=lambda: controller.show_frame(Planning))
button_navigate_planning.grid(row=9, column=1, pady=10, padx=10)
button_navigate_monitoring = ttk.Button(self, text='Monitoring', command=lambda: controller.show_frame(Monitoring))
button_navigate_monitoring.grid(row=9, column=2, pady=10, padx=10)
def validate_input(self, controller, real_task, privacy, efficiency, latest_index, dataframe):
""" Function that will be triggered when clicking on validation button
Args:
real_task (StringVar): task that has actually be completed during the previous sprint
privacy (StringVar): indicates whether the completed task was private or professional
efficiency (IntVar): rates the efficiency of the latest sprint from 1 (inefficient) to 4 (very efficient)
latest_index (Pandas.Series): full record corresponding to the latest task
dataframe (Pandas.DataFrame) : full database
"""
latest_index['Personal'] = privacy.get()
# Ensuring that latest task is not empty
if real_task.get() == '':
error_label = tk.Message(self, text='Please input your latest task',
font=SMALL_FONT, foreground='red', width=100, justify='center')
else:
latest_index['Actual task'] = real_task.get()
# Ensuring that efficiency is a number between 1 and 4
try:
if 1 <= efficiency.get() <= 4:
latest_index['Efficiency'] = efficiency.get()
else:
error_label = tk.Message(self, text='Please enter a value between 1 (inefficient) and 4 (very efficient) for efficiency',
font=SMALL_FONT, foreground='red', width=100, justify='center')
except:
error_label = tk.Message(self, text='Please input a value between 1 and 4 for efficiency',
font=SMALL_FONT, foreground='red', width=100, justify='center')
try:
error_label.grid(pady=2, padx=10, sticky='nsew', row=8, column=2)
# If error_label does not exist, the input is valid
except UnboundLocalError:
# The error message is converted into a success message
error_label = tk.Message(self, text='Succesfully updated', font=SMALL_FONT, foreground='green', width=100, justify='center')
error_label.grid(pady=2, padx=10, sticky='nsew', row=8, column=2)
# Dataframe is updated with the latest record
dataframe.set_index('Datetime', inplace=True)
latest_record = latest_index.to_frame().transpose()
latest_record.set_index('Datetime', inplace=True)
dataframe.update(latest_record)
header = ['Day', 'Time', 'Planned task', 'Personal', 'Actual task', 'Efficiency']
try:
dataframe[header].sort_index(ascending=True).to_csv('files/db_test.csv', index=False, sep=';')
except PermissionError:
error_label = tk.Message(self, text='File already open !', font=SMALL_FONT, foreground='red', width=100, justify='center')
error_label.grid(pady=2, padx=10, sticky='nsew', row=8, column=2)
# The message is displayed for 1 second, then we move to the next task
time.sleep(1) # Message not displayed
controller.show_frame(StartWorking)
class Planning(tk.Frame):
def __init__(self, parent, controller):
"""
Work page that will enable to plan tasks for a defined day.
Args:
parent (tkinter Frame): a widget (Window) to act as the parent of the current object
controller (tkinter Frame): object that is designed to act as a common point of interaction for several pages of widgets
"""
tk.Frame.__init__(self, parent)
# Main title
title = tk.Label(self, text='Planning', font=TITLE_FONT)
title.grid(pady=30,padx=10, sticky='nsew', row=0, column=0, columnspan=3)
# Input date of the day planned
date_label = tk.Label(self, text='Day planned (format DD/MM/YYYY)', font=LARGE_FONT, justify='center')
date_label.grid(pady=10,padx=10, sticky='nsew', row=1, column=0, columnspan=3)
date = tk.StringVar()
date_entry = ttk.Entry(self, textvariable=date)
date_entry.grid(pady=10,padx=10, sticky='nsew', row=2, column=1)
validate_icon = tk.PhotoImage(file=r'img/validate.png').subsample(10, 10)
validate_button = ttk.Button(self, text='Validate', image=validate_icon, compound='left',
command=lambda: self.validate_date(controller, date))
validate_button.grid(pady=30, padx=10, sticky='nsew', row=3, column=1)
# Navigation buttons to reach other pages
"""They actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Back to home', command=lambda: controller.show_frame(Welcome))
button_navigate_working.grid(row=9, column=0, pady=10, padx=10)
button_navigate_planning = ttk.Button(self, text='Start Working', command=lambda: controller.show_frame(StartWorking))
button_navigate_planning.grid(row=9, column=1, pady=10, padx=10)
button_navigate_monitoring = ttk.Button(self, text='Monitoring', command=lambda: controller.show_frame(Monitoring))
button_navigate_monitoring.grid(row=9, column=2, pady=10, padx=10)
def validate_date(self, controller, date):
"""Checks the validity of the input day.
Args:
date (StringVar): date of the day that is to be planned
"""
if date.get() == '':
error_label = tk.Message(self, text='Please input your latest task',
font=SMALL_FONT, foreground='red', width=140, justify='center')
else:
input_date = str(date.get())
try:
date_array = input_date.split('/')
day = int(date_array[0])
month = int(date_array[1])
year = int(date_array[2])
if day < 1 or 31 < day or month < 1 or 12 < month or year < 2020 or 2100 < year:
error_label = tk.Message(self, text='Please input a valid date',
font=SMALL_FONT, foreground='red', width=140, justify='center')
except (TypeError, ValueError, IndexError):
error_label = tk.Message(self, text='Please input a valid date (format DD/MM/YYYY)',
font=SMALL_FONT, foreground='red', width=140, justify='center')
try:
error_label.grid(pady=10, padx=10, sticky='nsew', row=4, column=1)
# If error_label does not exist, the input is valid
except UnboundLocalError:
# Swap to Planning window
controller.show_frame(Planning_tasks)
return str(date.get())
class Planning_tasks(ttk.Frame):
def __init__(self, parent, controller):
tk.ttk.Frame.__init__(self, parent)
# Main title
title = ttk.Label(self, text='Planning', font=TITLE_FONT)
title.grid(pady=20,padx=10, sticky='nsew', row=0, column=0, columnspan=3)
# Default sprints
sprints = pd.DataFrame({'Time': ['7:30', '8:00', '8:30', '9:00', '10:00', '10:30', '11:00', '13:00', '13:30', '14:00', '15:00', '15:30', '16:00', '16:30'],
'Planned_task_list': ['', '', '', '', '', '', '', '', '', '', '', '', '', '']})
sprints.set_index('Time', inplace=True)
for row in sprints:
pass
#print(row)
#label_start_time = tk.Label(self, text=row['Time'], font=LARGE_FONT, justify='center')
#label_start_time.grid(row=index+1, column=0, pady=10, padx=10)
#Déclencher au clic sur delete ou ajout
#lambda: self.display_sprints(sprints)
# Navigation buttons to reach other pages
"""They actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Back to home', command=lambda: controller.show_frame(Welcome))
button_navigate_working.grid(row=50, column=0, pady=10, padx=10)
button_navigate_planning = ttk.Button(self, text='Start Working', command=lambda: controller.show_frame(StartWorking))
button_navigate_planning.grid(row=50, column=1, pady=10, padx=10)
button_navigate_monitoring = ttk.Button(self, text='Monitoring', command=lambda: controller.show_frame(Monitoring))
button_navigate_monitoring.grid(row=50, column=2, pady=10, padx=10)
def display_sprints(self, sprints):
"""Displays the correct amount of sprints for the day
Args:
sprints (pandas.DataFrame): contains time (indexes) and planned task for each sprint
"""
for index, row in sprints:
label_start_time = tk.Label(self, text=row['Time'], font=LARGE_FONT, justify='center')
label_start_time.grid(row=index+1, column=0, pady=10, padx=10)
class Countdown(ttk.Frame):
def __init__(self, parent, controller):
tk.ttk.Frame.__init__(self, parent)
# Reading from database
dataframe = pd.read_csv('files/db_test.csv', sep=';')
# Converting strings objects into datetime objects
dataframe['Datetime'] = pd.to_datetime(dataframe['Day'] + ' ' + dataframe['Time'], format='%d/%m/%Y %H:%M')
dataframe['Deltatime'] = datetime.now() - dataframe['Datetime']
# Comparing to current datetime and identifying next planned task
future_filter = (dataframe['Deltatime'] < timedelta(0))
dataframe_future = dataframe.loc[(future_filter)]
# No need for a control on the existence of the value, as it has just been done in StartWorking frame
next_task = dataframe_future.loc[dataframe_future['Deltatime'].idxmax()]['Planned task']
# Display next task
label_task = tk.Label(self, text=next_task, font=LARGE_FONT)
label_task.grid(pady=80,padx=10, sticky='nsew', row=0, column=0)
# Go back to previous screen in case of interruption
button_interrupt = ttk.Button(self, text='Interrupt', command=lambda: controller.show_frame(StartWorking))
button_interrupt.grid(row=2, column=0, pady=10, padx=10, sticky='sew')
class Monitoring(ttk.Frame):
def __init__(self, parent, controller):
tk.ttk.Frame.__init__(self, parent)
title = ttk.Label(self, text='Monitoring', font=TITLE_FONT)
title.grid(pady=20,padx=10, sticky='nsew', row=0, column=0, columnspan=3)
# Navigation buttons to reach other pages
"""They actually raise other frames on top of the current one within the same container"""
button_navigate_working = ttk.Button(self, text='Back to home', command=lambda: controller.show_frame(Welcome))
button_navigate_working.grid(row=9, column=0, pady=10, padx=10)
button_navigate_planning = ttk.Button(self, text='Start Working', command=lambda: controller.show_frame(StartWorking))
button_navigate_planning.grid(row=9, column=1, pady=10, padx=10)
button_navigate_monitoring = ttk.Button(self, text='Planning', command=lambda: controller.show_frame(Planning))
button_navigate_monitoring.grid(row=9, column=2, pady=10, padx=10)
app = Window()
app.mainloop()
我尚未解决的两个问题:
编辑:由于您的帮助,第二个问题已解决。那我只想删除倒数标签。
如果发现任何其他错误,欢迎使用。 非常感谢!
答案 0 :(得分:1)
关于第二个问题:
当倒数达到0时(您还必须import os
):
os.system('TASKKILL /F /IM VLC.EXE')
这应该杀死所有vlc.exe
有关更多信息,请转到cmd并键入taskkill /?
并检查您的任务管理器