UI issues in my script

Im looking for help on a script. I want the user to be able to able to drag and resize the windows in the interface as well as when the window goes full screen the backgound image fills the window.

class YouTubeJukebox(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title("YouTube Jukebox by Littch")
        self.geometry("800x600")
        
        # Load background image
        self.bg_image = Image.open(r"C:\Users\Jack\Desktop\dj-8562307.jpg")
        self.bg_image = self.bg_image.resize((800, 600), Image.LANCZOS)
        self.bg_photo = ImageTk.PhotoImage(self.bg_image)

        # Create a canvas and set the background image
        self.canvas = tk.Canvas(self, width=800, height=600)
        self.canvas.pack(fill="both", expand=True)
        self.canvas.create_image(0, 0, image=self.bg_photo, anchor="nw")

        # VLC Instance and Player Setup
        self.vlc_instance = vlc.Instance()
        self.player = self.vlc_instance.media_player_new()
        self.player.event_manager().event_attach(vlc.EventType.MediaPlayerEndReached, self.on_song_end)

        # Styling
        style = ttk.Style(self)
        style.configure('TButton', font=('Arial', 7), padding=5)
        style.map('TButton', foreground=[('pressed', 'white'), ('active', 'white')],
                  background=[('pressed', '#3498DB'), ('active', '#3498DB')])

        self.playlist_id_entry_style = {"bg": "#ECF0F1", "fg": "#34495E", "insertbackground": "#34495E"}
        self.playlist_id_label_style = {"bg": "#2C3E50", "fg": "#FFFFFF"}

        # Add widgets to the canvas
        self.label = tk.Label(self, text="Enter YouTube Playlist ID:", **self.playlist_id_label_style)
        self.label_window = self.canvas.create_window(400, 50, anchor="center", window=self.label)

        self.playlist_id_entry = tk.Entry(self, width=50, **self.playlist_id_entry_style)
        self.entry_window = self.canvas.create_window(400, 100, anchor="center", window=self.playlist_id_entry)

        self.load_button = ttk.Button(self, text="Load Playlist", command=self.load_playlist)
        self.load_button_window = self.canvas.create_window(400, 150, anchor="center", window=self.load_button)

        self.listbox = tk.Listbox(self, width=80, height=15, bg="#424242", fg="#FFFFFF", selectbackground="#FF5722")
        self.listbox_window = self.canvas.create_window(400, 300, anchor="center", window=self.listbox)

        self.controls_frame = tk.Frame(self, bg="#212121")
        self.controls_frame_window = self.canvas.create_window(400, 450, anchor="center", window=self.controls_frame)

        button_style = {'width': 6, 'height': 1}

        self.play_button = ttk.Button(self.controls_frame, text="Play", command=self.play_selected)
        self.play_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.pause_button = ttk.Button(self.controls_frame, text="Pause", command=self.pause_audio)
        self.pause_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.play_all_button = ttk.Button(self.controls_frame, text="Play All", command=self.play_all)
        self.play_all_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.skip_back_button = ttk.Button(self.controls_frame, text="Skip Back", command=self.skip_back)
        self.skip_back_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.skip_forward_button = ttk.Button(self.controls_frame, text="Skip Forward", command=self.skip_forward)
        self.skip_forward_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.shuffle_button = ttk.Button(self.controls_frame, text="Shuffle", command=self.shuffle_playlist)
        self.shuffle_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.save_button = ttk.Button(self.controls_frame, text="Save Playlist", command=self.save_playlist)
        self.save_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.load_playlist_button = ttk.Button(self.controls_frame, text="Load Playlist", command=self.load_saved_playlist)
        self.load_playlist_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.volume_label = tk.Label(self, text="Volume:", fg="#FFFFFF", bg="#212121")
        self.volume_label_window = self.canvas.create_window(200, 500, anchor="center", window=self.volume_label)

        self.volume_slider = tk.Scale(self, from_=0, to=100, orient=tk.HORIZONTAL, command=self.set_volume, length=300)
        self.volume_slider.set(50)
        self.volume_slider_window = self.canvas.create_window(400, 550, anchor="center", window=self.volume_slider)

        self.equalizer_frame = tk.Frame(self, bg="#212121")
        self.equalizer_frame_window = self.canvas.create_window(400, 600, anchor="center", window=self.equalizer_frame)

        self.equalizer = vlc.AudioEqualizer()
        self.create_equalizer_controls()

        self.playlist = None
        self.current_index = None
        self.audio_urls = []
        self.media_list = None
        self.media_player = None

        self.last_playlists_file = "last_playlists.json"
        self.last_playlists = []

        # Bindings for moving and resizing
        self.bind_events()

    def bind_events(self):
        for widget in [self.label, self.playlist_id_entry, self.load_button, self.listbox, self.controls_frame, self.volume_label, self.volume_slider, self.equalizer_frame]:
            widget.bind("<Button-1>", self.on_widget_click)
            widget.bind("<B1-Motion>", self.on_drag)
            widget.bind("<ButtonRelease-1>", self.on_release)
            widget.bind("<Enter>", self.on_enter)
            widget.bind("<Leave>", self.on_leave)

        self.drag_data = {"x": 0, "y": 0, "widget": None}
        self.resize_data = {"resizing": False, "widget": None, "direction": None}

    def on_widget_click(self, event):
        widget = event.widget
        self.drag_data["widget"] = widget
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y
        if self.resize_data["resizing"]:
            self.resize_data["widget"] = widget

    def on_drag(self, event):
        widget = self.drag_data["widget"]
        if widget and not self.resize_data["resizing"]:
            x = widget.winfo_x() + event.x - self.drag_data["x"]
            y = widget.winfo_y() + event.y - self.drag_data["y"]
            widget.place(x=x, y=y)
        elif self.resize_data["resizing"]:
            self.resize_widget(event)

    def on_release(self, event):
        self.drag_data["widget"] = None
        self.resize_data["resizing"] = False

    def on_enter(self, event):
        widget = event.widget
        widget.update_idletasks()
        bbox = widget.winfo_geometry().split('+')[0].split('x')
        width = int(bbox[0])
        height = int(bbox[1])

        if abs(event.x - width) < 5 or abs(event.y - height) < 5:
            self.canvas.config(cursor="sizing")
            self.resize_data["resizing"] = True
        else:
            self.canvas.config(cursor="")

    def on_leave(self, event):
        self.canvas.config(cursor="")

    def resize_widget(self, event):
        widget = self.resize_data["widget"]
        widget.update_idletasks()
        width = widget.winfo_width()
        height = widget.winfo_height()

        if abs(event.x - width) < 5:
            widget.config(width=event.x)
        elif abs(event.y - height) < 5:
            widget.config(height=event.y)


All the logic with this script works I have the complete script everything works but the logic to make the interface windows moveable and resizable by the user. Also when the window is open to full screen the script does not adapt to the new resolution.

IDLE is a tkinter app. Editor windows and some dialogs, such as Search (idlelib.searchbase) are resizeable. The Settings dialog (idlelib.configdialog) is not. I believe toplevels are resizable by default. The setup of Settings includes self.resizable(height=FALSE, width=FALSE) to override this. I am not sure what your problem is.

I believe that there is a ‘fullscreen’ event that you can capture, but I don’t remember the precise name.

If no answer here, stackoverflow.com has a lot of tkinter Q&As.


Im trying to come up with the logic to resize the windows in this script. I can drag them around the screen but I cant get the logic right to resize the widgets/windows

Does this help:

what i am looking for is the grab edges of the window and drag and resize type logic

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from yt_dlp import YoutubeDL
import vlc
import os
import random
import json
import threading
from PIL import Image, ImageTk

class YouTubeJukebox(tk.Tk):
    def __init__(self):
        super().__init__()

        self.title("YouTube Jukebox by Littch")
        
        # Detect screen width and height
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()

        # Set geometry to maximize the window
        self.geometry(f"{screen_width}x{screen_height}")
        
        # Load background image
        self.bg_image = Image.open(r"C:\Users\Jack\Desktop\dj-8562307.jpg")
        self.bg_image = self.bg_image.resize((screen_width, screen_height), Image.LANCZOS)
        self.bg_photo = ImageTk.PhotoImage(self.bg_image)

        # Create a canvas and set the background image
        self.canvas = tk.Canvas(self, width=screen_width, height=screen_height)
        self.canvas.pack(fill="both", expand=True)
        self.canvas.create_image(0, 0, image=self.bg_photo, anchor="nw")

        # VLC Instance and Player Setup
        self.vlc_instance = vlc.Instance()
        self.player = self.vlc_instance.media_player_new()
        self.player.event_manager().event_attach(vlc.EventType.MediaPlayerEndReached, self.on_song_end)

        # Styling
        style = ttk.Style(self)
        style.configure('TButton', font=('Arial', 7), padding=5)
        style.map('TButton', foreground=[('pressed', 'white'), ('active', 'white')],
                  background=[('pressed', '#3498DB'), ('active', '#3498DB')])

        self.playlist_id_entry_style = {"bg": "#ECF0F1", "fg": "#34495E", "insertbackground": "#34495E"}
        self.playlist_id_label_style = {"bg": "#2C3E50", "fg": "#FFFFFF"}

        # Add widgets to the canvas
        self.label = tk.Label(self, text="Enter YouTube Playlist ID:", **self.playlist_id_label_style)
        self.label_window = self.canvas.create_window(screen_width//2, 50, anchor="center", window=self.label)

        self.playlist_id_entry = tk.Entry(self, width=50, **self.playlist_id_entry_style)
        self.entry_window = self.canvas.create_window(screen_width//2, 100, anchor="center", window=self.playlist_id_entry)

        self.load_button = ttk.Button(self, text="Load Playlist", command=self.load_playlist)
        self.load_button_window = self.canvas.create_window(screen_width//2, 150, anchor="center", window=self.load_button)

        self.listbox = tk.Listbox(self, width=80, height=15, bg="#424242", fg="#FFFFFF", selectbackground="#FF5722")
        self.listbox_window = self.canvas.create_window(screen_width//2, 300, anchor="center", window=self.listbox)

        self.controls_frame = tk.Frame(self, bg="#212121")
        self.controls_frame_window = self.canvas.create_window(screen_width//2, 450, anchor="center", window=self.controls_frame)

        button_style = {'width': 6, 'height': 1}

        self.play_button = ttk.Button(self.controls_frame, text="Play", command=self.play_selected)
        self.play_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.pause_button = ttk.Button(self.controls_frame, text="Pause", command=self.pause_audio)
        self.pause_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.play_all_button = ttk.Button(self.controls_frame, text="Play All", command=self.play_all)
        self.play_all_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.skip_back_button = ttk.Button(self.controls_frame, text="Skip Back", command=self.skip_back)
        self.skip_back_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.skip_forward_button = ttk.Button(self.controls_frame, text="Skip Forward", command=self.skip_forward)
        self.skip_forward_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.shuffle_button = ttk.Button(self.controls_frame, text="Shuffle", command=self.shuffle_playlist)
        self.shuffle_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.save_button = ttk.Button(self.controls_frame, text="Save Playlist", command=self.save_playlist)
        self.save_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.load_playlist_button = ttk.Button(self.controls_frame, text="Load Playlist", command=self.load_saved_playlist)
        self.load_playlist_button.pack(side=tk.LEFT, padx=5, pady=5)

        self.volume_label = tk.Label(self, text="Volume:", fg="#FFFFFF", bg="#212121")
        self.volume_label_window = self.canvas.create_window(200, 500, anchor="center", window=self.volume_label)

        self.volume_slider = tk.Scale(self, from_=0, to=100, orient=tk.HORIZONTAL, command=self.set_volume, length=300)
        self.volume_slider.set(50)
        self.volume_slider_window = self.canvas.create_window(screen_width//2, 550, anchor="center", window=self.volume_slider)

        self.equalizer_frame = tk.Frame(self, bg="#212121")
        self.equalizer_frame_window = self.canvas.create_window(screen_width//2, 600, anchor="center", window=self.equalizer_frame)

        self.equalizer = vlc.AudioEqualizer()
        self.create_equalizer_controls()

        self.playlist = None
        self.current_index = None
        self.audio_urls = []
        self.media_list = None
        self.media_player = None

        self.last_playlists_file = "last_playlists.json"
        self.last_playlists = []

        # Bindings for moving and resizing
        self.bind_events()

    def bind_events(self):
        for widget in [self.label, self.playlist_id_entry, self.load_button, self.listbox, self.controls_frame, self.volume_label, self.volume_slider, self.equalizer_frame]:
            widget.bind("<Button-1>", self.on_widget_click)
            widget.bind("<B1-Motion>", self.on_drag)
            widget.bind("<ButtonRelease-1>", self.on_release)
            widget.bind("<Enter>", self.on_enter)
            widget.bind("<Leave>", self.on_leave)

        self.drag_data = {"x": 0, "y": 0, "widget": None}
        self.resize_data = {"resizing": False, "widget": None, "direction": None}

    def on_widget_click(self, event):
        widget = event.widget
        self.drag_data["widget"] = widget
        self.drag_data["x"] = event.x
        self.drag_data["y"] = event.y
        if self.resize_data["resizing"]:
            self.resize_data["widget"] = widget

    def on_drag(self, event):
        widget = self.drag_data["widget"]
        if widget and not self.resize_data["resizing"]:
            x = widget.winfo_x() + event.x - self.drag_data["x"]
            y = widget.winfo_y() + event.y - self.drag_data["y"]
            widget.place(x=x, y=y)
        elif self.resize_data["resizing"]:
            self.resize_widget(event)

    def on_release(self, event):
        self.drag_data["widget"] = None
        self.resize_data["resizing"] = False

    def on_enter(self, event):
        widget = event.widget
        widget.update_idletasks()
        bbox = widget.winfo_geometry().split('+')[0].split('x')
        width = int(bbox[0])
        height = int(bbox[1])

        if abs(event.x - width) < 5 or abs(event.y - height) < 5:
            self.canvas.config(cursor="sizing")
            self.resize_data["resizing"] = True
        else:
            self.canvas.config(cursor="")

    def on_leave(self, event):
        self.canvas.config(cursor="")

    def resize_widget(self, event):
        widget = self.resize_data["widget"]
        widget.update_idletasks()
        width = widget.winfo_width()
        height = widget.winfo_height()

        if abs(event.x - width) < 5:
            widget.config(width=event.x)
        elif abs(event.y - height) < 5:
            widget.config(height=event.y)

    def load_playlist(self):
        def load_playlist_async():
            try:
                playlist_id = self.playlist_id_entry.get().split('&')[0]
                if not playlist_id:
                    messagebox.showerror("Error", "Please enter a valid playlist ID")
                    return

                playlist_url = f"https://www.youtube.com/playlist?list={playlist_id}"
                self.playlist = self.get_playlist_videos(playlist_url)

                self.listbox.delete(0, tk.END)
                for title in [video['title'] for video in self.playlist]:
                    self.listbox.insert(tk.END, title)

                self.current_index = 0
            except Exception as e:
                print(f"Error loading playlist: {e}")
                messagebox.showerror("Error", f"Failed to load playlist: {e}")

        threading.Thread(target=load_playlist_async).start()

    def get_playlist_videos(self, playlist_url):
        ydl_opts = {
            'extract_flat': True,
            'quiet': True,
            'force_generic_extractor': True
        }
        with YoutubeDL(ydl_opts) as ydl:
            result = ydl.extract_info(playlist_url, download=False)
            if 'entries' in result:
                return result['entries']
            else:
                return []

    def play_all(self):
        def play_all_async():
            try:
                if not self.playlist:
                    messagebox.showerror("Error", "Please load a playlist first")
                    return
                self.current_index = 0
                self.play_current_index()
            except Exception as e:
                print(f"Error in play_all: {e}")
        threading.Thread(target=play_all_async).start()

    def play_selected(self):
        def play_selected_async():
            try:
                if not self.playlist:
                    messagebox.showerror("Error", "Please load a playlist")
                    return
                selected_index = self.listbox.curselection()
                if selected_index:
                    self.current_index = selected_index[0]
                self.play_current_index()
            except Exception as e:
                print(f"Error in play_selected: {e}")
        threading.Thread(target=play_selected_async).start()

    def play_current_index(self):
        def play_current_index_async():
            try:
                if self.current_index is None or self.current_index >= len(self.playlist):
                    messagebox.showerror("Error", "Please select a valid video")
                    return
                video_url = f"https://www.youtube.com/watch?v={self.playlist[self.current_index]['id']}"
                audio_url = self.get_best_audio_url(video_url)
                media = self.vlc_instance.media_new(audio_url)
                self.player.set_media(media)
                self.set_volume(self.volume_slider.get())
                self.player.play()
                self.highlight_current_song()
            except Exception as e:
                print(f"Error in play_current_index: {e}")

        threading.Thread(target=play_current_index_async).start()

    def get_best_audio_url(self, video_url):
        ydl_opts = {
            'format': 'bestaudio',
            'quiet': True
        }
        with YoutubeDL(ydl_opts) as ydl:
            info_dict = ydl.extract_info(video_url, download=False)
            return info_dict['url']

    def highlight_current_song(self):
        try:
            self.listbox.select_clear(0, tk.END)
            self.listbox.select_set(self.current_index)
            self.listbox.activate(self.current_index)
        except Exception as e:
            print(f"Error in highlight_current_song: {e}")

    def pause_audio(self):
        try:
            if self.player.is_playing():
                self.player.pause()
        except Exception as e:
            print(f"Error in pause_audio: {e}")

    def on_song_end(self, event):
        self.current_index += 1
        if self.current_index < len(self.playlist):
            self.play_current_index()

    def skip_forward(self):
        try:
            if self.current_index is not None:
                self.current_index = (self.current_index + 1) % len(self.playlist)
                self.play_current_index()
        except Exception as e:
            print(f"Error in skip_forward: {e}")

    def skip_back(self):
        try:
            if self.current_index is not None:
                self.current_index = (self.current_index - 1) % len(self.playlist)
                self.play_current_index()
        except Exception as e:
            print(f"Error in skip_back: {e}")

    def shuffle_playlist(self):
        try:
            if self.playlist:
                random.shuffle(self.playlist)
                self.listbox.delete(0, tk.END)
                for video in self.playlist:
                    self.listbox.insert(tk.END, video['title'])
                self.current_index = 0
        except Exception as e:
            print(f"Error in shuffle_playlist: {e}")

    def set_volume(self, volume):
        try:
            self.player.audio_set_volume(int(volume))
        except Exception as e:
            print(f"Error in set_volume: {e}")

    def save_playlist(self):
        try:
            playlist_id = self.playlist_id_entry.get().split('&')[0]
            self.load_last_playlists()
            if playlist_id and playlist_id not in self.last_playlists:
                self.last_playlists.append(playlist_id)
                with open(self.last_playlists_file, "w") as file:
                    json.dump(self.last_playlists, file)
                messagebox.showinfo("Success", "Playlist saved successfully")
            else:
                messagebox.showinfo("Info", "Playlist is already saved")
        except Exception as e:
            print(f"Error in save_playlist: {e}")

    def load_last_playlists(self):
        try:
            if os.path.exists(self.last_playlists_file):
                with open(self.last_playlists_file, "r") as file:
                    self.last_playlists = json.load(file)
            else:
                self.last_playlists = []
        except Exception as e:
            print(f"Error in load_last_playlists: {e}")

    def load_saved_playlist(self):
        try:
            self.load_last_playlists()
            if not self.last_playlists:
                messagebox.showinfo("Info", "No saved playlists found")
                return

            playlist_id = random.choice(self.last_playlists)
            self.playlist_id_entry.delete(0, tk.END)
            self.playlist_id_entry.insert(0, playlist_id)
            self.load_playlist()
        except Exception as e:
            print(f"Error in load_saved_playlist: {e}")

    def create_equalizer_controls(self):
        labels = ["60Hz", "170Hz", "310Hz", "600Hz", "1kHz", "3kHz", "6kHz", "12kHz", "14kHz", "16kHz"]
        for i, label in enumerate(labels):
            lbl = tk.Label(self.equalizer_frame, text=label, fg="#FFFFFF", bg="#212121")
            lbl.grid(row=0, column=i, padx=2, pady=2)

            scale = tk.Scale(self.equalizer_frame, from_=-20, to=20, orient=tk.HORIZONTAL, length=70, command=lambda val, idx=i: self.set_equalizer_band(idx, val))
            scale.grid(row=1, column=i, padx=2, pady=2)

    def set_equalizer_band(self, band, value):
        try:
            self.equalizer.set_amp_at_index(float(value), band)
            self.player.set_equalizer(self.equalizer)
        except Exception as e:
            print(f"Error setting equalizer band: {e}")

if __name__ == "__main__":
    app = YouTubeJukebox()
    app.mainloop()

is where i am at currently on this
I can move things around screen, but i want the user to be able to grab the edges of windows/widgets and resize them as they want.


Img i use for background

couple of playlist
PL2LiUfPpNU4qo8Fs1rwnDeml4X-22Xx_v
PL2LiUfPpNU4q4pMLasVnAUXWdHBbifc0b

Ok, so you want to have independent widget movement/placement and resizing control as shown here (for the widget shown).

move_resize_widget

Legend:

A - initial location and size

B - post widget placement and resizing

This about right?

The mouse events when resizing a window by dragging the conners of
the window are not handled by Tk, but earlier by the window manager.

You can bind the “” event to be notified when the window
has been resized:

import tkinter as tk

class App(tk.Tk):
     def __init__(self):
         super().__init__()
         self.bind("<Configure>", self.resize)

     def resize(widget, event):
         print(widget, event.x, event.y, event.width, event.height)

if __name__ == '__main__':
     app =  App()
     app.mainloop()

However, this is much easier with a layout manager (like a Grid
layout) where you don’t need to calculate the new sub-widgets new
sizes and positions.

what i am looking for is when resizing a window/widget the double arrows appear when you get near the edge of the window and you click down on your mouse and grab and drag it to resize it. So like when the mouse nears the edge of the “Listbox song Window” you can drag and resize it, the volume bar, the equalizer bar, etc any window on the screen can be manipulated “resized” with the double arrows appear when nearing the edge of said window and change the height width by dragging up/down left/right when hovering over a edge and double arrows take over the cursor icon.