销毁Tkinter标签并关闭VLC

时间:2020-10-28 22:47:37

标签: python tkinter

我目前正在尝试开发一个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()

我尚未解决的两个问题:

  • 我无法使用Countdown类中的函数显示倒计时,因此我将其传递给“ Window”类。我觉得这不是一个干净的方法,但是我还没有找到解决方案。另外,当倒数时间为零时,该帧会正确地更改为SprintEnd帧,但标签不会被破坏:它仍然可见,并在SprintEnd帧的顶部显示“ 00:02”。
  • 我想在倒数到零时关闭VLC媒体播放器,但是我在网上发现的执行此操作的几个示例似乎不起作用。

编辑:由于您的帮助,第二个问题已解决。那我只想删除倒数标签。

如果发现任何其他错误,欢迎使用。 非常感谢!

1 个答案:

答案 0 :(得分:1)

关于第二个问题: 当倒数达到0时(您还必须import os):

os.system('TASKKILL /F /IM VLC.EXE')

这应该杀死所有vlc.exe 有关更多信息,请转到cmd并键入taskkill /?并检查您的任务管理器