最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - Adding a Scrollbar in Tkinter - Stack Overflow

programmeradmin1浏览0评论

I want to add a scroll bar in the second GUI where all the lists (card layout form) of servers are displayed how to do it . Im new in programming and tried using canvas and the scrollbar but it just changes the width of the card layout. I want it to be static Here is the code:

import tkinter as tk
from tkinter import messagebox, ttk
from backend import start_backend, servers  # Import the backend logic
import threading
import requests    

# Flag to track if the load balancer is running
is_running = False
backend_thread = None  # Thread for the backend Flask app


def start_load_balancer():
    """Starts the load balancer."""
    global is_running, backend_thread
    if not is_running:
        backend_thread = threading.Thread(target=start_backend, daemon=True)
        backend_thread.start()
        is_running = True
        status_label.config(text="Load Balancer: Running", fg="green")
    else:
        messagebox.showinfo("Info", "Load Balancer is already running!")


def stop_load_balancer():
    """Stops the load balancer."""
    global is_running
    if is_running:
        messagebox.showinfo("Info", "To stop the load balancer, manually terminate the process (CTRL+C in terminal).")
        status_label.config(text="Load Balancer: Stopped", fg="red")
        is_running = False
    else:
        messagebox.showinfo("Info", "Load Balancer is not running!")


def toggle_logs(server_index, log_table):
    """Toggles the visibility of the log table."""
    if log_table.winfo_ismapped():
        log_table.grid_remove()  # Hide the table
    else:
        log_table.grid()  # Show the table


def update_server_statuses(cards, log_tables):
    """Updates the status and logs of each server dynamically."""
    for server, card, log_table in zip(servers, cards, log_tables):
        try:
            response = requests.get(server["url"], timeout=2)
            if response.status_code == 200:
                server["status"] = "Active"
            else:
                server["status"] = "Inactive"
        except requests.exceptions.ConnectionError:
            server["status"] = "Inactive"

        # Update the card with the current status and request count
        card.config(
            text=f"Name: Server\nIP: {server['url']}\nStatus: {server['status']}\nRequests: {server['request_count']}",
            fg="green" if server["status"] == "Active" else "red",
        )

        # Clear and update the log table, even if hidden
        for i in log_table.get_children():
            log_table.delete(i)
        for log in server.get("logs", []):
            log_table.insert("", "end", values=(log["timestamp"], log["method"], log["path"]))

    # Repeat every 5 seconds
    root.after(5000, lambda: update_server_statuses(cards, log_tables))


def initialize_main_gui():
    """Initializes the main GUI for the Load Balancer."""
    global root, status_label
    root = tk.Tk()
    root.title("Load Balancer GUI")
    root.geometry("1280x720")

    # Title Label
    title_label = tk.Label(root, text="Load Balancer - Server Statuses", font=("Arial", 20, "bold"))
    title_label.pack(pady=20)

    # Frame for control buttons
    button_frame = tk.Frame(root, bg="#f7f7f7")
    button_frame.pack(pady=10)

    # Start and Stop Buttons
    start_button = tk.Button(
        button_frame,
        text="Start Load Balancer",
        command=start_load_balancer,
        bg="#4CAF50",
        fg="white",
        font=("Arial", 14),
        width=20
    )
    start_button.grid(row=0, column=0, padx=10)

    stop_button = tk.Button(
        button_frame,
        text="Stop Load Balancer",
        command=stop_load_balancer,
        bg="#f44336",
        fg="white",
        font=("Arial", 14),
        width=20
    )
    stop_button.grid(row=0, column=1, padx=10)

    # Status Label
    global status_label
    status_label = tk.Label(root, text="Load Balancer: Stopped", fg="red", font=("Arial", 16))
    status_label.pack(pady=10)

    # Frame for card layout
    card_frame = tk.Frame(root, bg="#f7f7f7")
    card_frame.pack(pady=20, padx=20, fill="both", expand=True)

    # Create cards and log tables for each server
    cards = []
    log_tables = []

    for index, server in enumerate(servers):
        # Frame for card and dropdown
        server_frame = tk.Frame(card_frame, bg="white", bd=4, relief="ridge", padx=10, pady=10)
        server_frame.pack(pady=10, fill="x", padx=10)
        server_frame.grid_columnconfigure(0, weight=1)  # Card (expandable column)
        server_frame.grid_columnconfigure(1, weight=0)  # Spacer
        server_frame.grid_columnconfigure(2, weight=0)  # Logs button column


        # Card to display server information
        card = tk.Label(
            server_frame,
            text=f"Name: Server\nIP: {server['url']}\nStatus: {server['status']}\nRequests: {server['request_count']}",
            font=("Arial", 14),
            bg="white",
            fg="orange",
            anchor="w",
            justify="left",
        )
        card.grid(row=0, column=0, sticky="w")
        cards.append(card)

        # Dropdown button to toggle logs
        dropdown_button = tk.Button(
            server_frame,
            text="Show Logs",
            command=lambda i=index: toggle_logs(i, log_tables[i]),
            bg="#2196F3",
            fg="white",
            font=("Arial", 12),
        )
        dropdown_button.grid(row=0, column=2, sticky="e", padx=20)


        # Log table (initially hidden)
        log_table = ttk.Treeview(server_frame, columns=("Timestamp", "Method", "Path"), show="headings", height=5)
        log_table.heading("Timestamp", text="Timestamp")
        log_table.heading("Method", text="Method")
        log_table.heading("Path", text="Path")
        log_table.column("Timestamp", width=200)
        log_table.column("Method", width=100)
        log_table.column("Path", width=200)
        log_table.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=10, pady=10)
        log_table.grid_remove()  # Start hidden
        log_tables.append(log_table)

    # Update server statuses and logs periodically
    update_server_statuses(cards, log_tables)

    root.mainloop()


def add_server():
    """Adds a new server with IP and Port to the backend configuration."""
    server_ip = ip_entry.get()
    server_port = port_entry.get()
    
    if server_ip and server_port:
        try:
            # Ensure the port is a valid integer
            server_port = int(server_port)
            servers.append({"url": f"http://{server_ip}:{server_port}", "connections": 0, "request_count": 0, "status": "Unknown", "logs": []})
            server_list.insert(tk.END, f"{server_ip}:{server_port}")
            ip_entry.delete(0, tk.END)
            port_entry.delete(0, tk.END)
        except ValueError:
            messagebox.showwarning("Warning", "Please enter a valid port number!")
    else:
        messagebox.showwarning("Warning", "Please enter both IP address and port!")


def delete_server():
    """Deletes the selected server from the list and backend configuration."""
    selected_index = server_list.curselection()
    if selected_index:
        selected_server = server_list.get(selected_index)
        server_list.delete(selected_index)
        servers[:] = [server for server in servers if f"{server['url'].split('//')[1]}" != selected_server]
        messagebox.showinfo("Info", f"Server {selected_server} removed successfully!")
    else:
        messagebox.showwarning("Warning", "Please select a server to delete!")


def proceed_to_main_gui():
    """Proceeds to the main GUI if servers are added."""
    if not servers:
        messagebox.showwarning("Warning", "Please add at least one server before proceeding!")
        return
    initial_gui.destroy()
    initialize_main_gui()


# Initial GUI to add servers
initial_gui = tk.Tk()
initial_gui.title("Add Backend Servers")
initial_width = 900
initial_height = 800

# Center the window on the screen
screen_width = initial_gui.winfo_screenwidth()
screen_height = initial_gui.winfo_screenheight()
x_position = (screen_width // 2) - (initial_width // 2)
y_position = (screen_height // 2) - (initial_height // 2)

initial_gui.geometry(f"{initial_width}x{initial_height}+{x_position}+{y_position}")
initial_gui.configure(bg="#f7f7f7")

# Title Label
title_label = tk.Label(
    initial_gui,
    text="Load Balancer Configuration",
    font=("Arial", 20, "bold"),
    bg="#f7f7f7",
    fg="#333"
)
title_label.pack(pady=20)

# Instructions Label
instruction_label = tk.Label(
    initial_gui,
    text="Enter the IP address and port of backend servers and click 'Add'.\nYou can also delete servers before proceeding.",
    font=("Arial", 14),
    bg="#f7f7f7",
    fg="#555"
)
instruction_label.pack(pady=10)

# Input Fields for Server IP and Port
input_frame = tk.Frame(initial_gui, bg="#f7f7f7")
input_frame.pack(pady=10)

ip_label = tk.Label(input_frame, text="IP Address:", font=("Arial", 12), bg="#f7f7f7")
ip_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
ip_entry = tk.Entry(input_frame, font=("Arial", 12), width=30, bd=2, relief="groove")
ip_entry.grid(row=0, column=1, padx=5, pady=5)

port_label = tk.Label(input_frame, text="Port:", font=("Arial", 12), bg="#f7f7f7")
port_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
port_entry = tk.Entry(input_frame, font=("Arial", 12), width=10, bd=2, relief="groove")
port_entry.grid(row=1, column=1, padx=5, pady=5, sticky="w")

# Buttons for Adding and Deleting Servers
button_frame = tk.Frame(initial_gui, bg="#f7f7f7")
button_frame.pack(pady=10)

add_button = tk.Button(
    button_frame,
    text="Add Server",
    command=add_server,
    bg="#4CAF50",
    fg="white",
    font=("Arial", 14),
    width=15
)
add_button.grid(row=0, column=0, padx=10)

delete_button = tk.Button(
    button_frame,
    text="Delete Server",
    command=delete_server,
    bg="#f44336",
    fg="white",
    font=("Arial", 14),
    width=15
)
delete_button.grid(row=0, column=1, padx=10)

# Listbox to Display Added Servers
server_list_frame = tk.Frame(initial_gui, bg="#f7f7f7")
server_list_frame.pack(pady=10)

server_list_label = tk.Label(
    server_list_frame,
    text="Added Servers:",
    font=("Arial", 16),
    bg="#f7f7f7",
    fg="#333"
)
server_list_label.pack(anchor="w")

server_list = tk.Listbox(server_list_frame, font=("Arial", 14), width=60, height=10, bd=2, relief="groove")
server_list.pack(pady=5)

# Proceed Button
proceed_button = tk.Button(
    initial_gui,
    text="Proceed",
    command=proceed_to_main_gui,
    bg="#2196F3",
    fg="white",
    font=("Arial", 16),
    width=20
)
proceed_button.pack(pady=30)

# Run the Initial GUI
initial_gui.mainloop()

I want to add a scroll bar in the second GUI where all the lists (card layout form) of servers are displayed how to do it . Im new in programming and tried using canvas and the scrollbar but it just changes the width of the card layout. I want it to be static Here is the code:

import tkinter as tk
from tkinter import messagebox, ttk
from backend import start_backend, servers  # Import the backend logic
import threading
import requests    

# Flag to track if the load balancer is running
is_running = False
backend_thread = None  # Thread for the backend Flask app


def start_load_balancer():
    """Starts the load balancer."""
    global is_running, backend_thread
    if not is_running:
        backend_thread = threading.Thread(target=start_backend, daemon=True)
        backend_thread.start()
        is_running = True
        status_label.config(text="Load Balancer: Running", fg="green")
    else:
        messagebox.showinfo("Info", "Load Balancer is already running!")


def stop_load_balancer():
    """Stops the load balancer."""
    global is_running
    if is_running:
        messagebox.showinfo("Info", "To stop the load balancer, manually terminate the process (CTRL+C in terminal).")
        status_label.config(text="Load Balancer: Stopped", fg="red")
        is_running = False
    else:
        messagebox.showinfo("Info", "Load Balancer is not running!")


def toggle_logs(server_index, log_table):
    """Toggles the visibility of the log table."""
    if log_table.winfo_ismapped():
        log_table.grid_remove()  # Hide the table
    else:
        log_table.grid()  # Show the table


def update_server_statuses(cards, log_tables):
    """Updates the status and logs of each server dynamically."""
    for server, card, log_table in zip(servers, cards, log_tables):
        try:
            response = requests.get(server["url"], timeout=2)
            if response.status_code == 200:
                server["status"] = "Active"
            else:
                server["status"] = "Inactive"
        except requests.exceptions.ConnectionError:
            server["status"] = "Inactive"

        # Update the card with the current status and request count
        card.config(
            text=f"Name: Server\nIP: {server['url']}\nStatus: {server['status']}\nRequests: {server['request_count']}",
            fg="green" if server["status"] == "Active" else "red",
        )

        # Clear and update the log table, even if hidden
        for i in log_table.get_children():
            log_table.delete(i)
        for log in server.get("logs", []):
            log_table.insert("", "end", values=(log["timestamp"], log["method"], log["path"]))

    # Repeat every 5 seconds
    root.after(5000, lambda: update_server_statuses(cards, log_tables))


def initialize_main_gui():
    """Initializes the main GUI for the Load Balancer."""
    global root, status_label
    root = tk.Tk()
    root.title("Load Balancer GUI")
    root.geometry("1280x720")

    # Title Label
    title_label = tk.Label(root, text="Load Balancer - Server Statuses", font=("Arial", 20, "bold"))
    title_label.pack(pady=20)

    # Frame for control buttons
    button_frame = tk.Frame(root, bg="#f7f7f7")
    button_frame.pack(pady=10)

    # Start and Stop Buttons
    start_button = tk.Button(
        button_frame,
        text="Start Load Balancer",
        command=start_load_balancer,
        bg="#4CAF50",
        fg="white",
        font=("Arial", 14),
        width=20
    )
    start_button.grid(row=0, column=0, padx=10)

    stop_button = tk.Button(
        button_frame,
        text="Stop Load Balancer",
        command=stop_load_balancer,
        bg="#f44336",
        fg="white",
        font=("Arial", 14),
        width=20
    )
    stop_button.grid(row=0, column=1, padx=10)

    # Status Label
    global status_label
    status_label = tk.Label(root, text="Load Balancer: Stopped", fg="red", font=("Arial", 16))
    status_label.pack(pady=10)

    # Frame for card layout
    card_frame = tk.Frame(root, bg="#f7f7f7")
    card_frame.pack(pady=20, padx=20, fill="both", expand=True)

    # Create cards and log tables for each server
    cards = []
    log_tables = []

    for index, server in enumerate(servers):
        # Frame for card and dropdown
        server_frame = tk.Frame(card_frame, bg="white", bd=4, relief="ridge", padx=10, pady=10)
        server_frame.pack(pady=10, fill="x", padx=10)
        server_frame.grid_columnconfigure(0, weight=1)  # Card (expandable column)
        server_frame.grid_columnconfigure(1, weight=0)  # Spacer
        server_frame.grid_columnconfigure(2, weight=0)  # Logs button column


        # Card to display server information
        card = tk.Label(
            server_frame,
            text=f"Name: Server\nIP: {server['url']}\nStatus: {server['status']}\nRequests: {server['request_count']}",
            font=("Arial", 14),
            bg="white",
            fg="orange",
            anchor="w",
            justify="left",
        )
        card.grid(row=0, column=0, sticky="w")
        cards.append(card)

        # Dropdown button to toggle logs
        dropdown_button = tk.Button(
            server_frame,
            text="Show Logs",
            command=lambda i=index: toggle_logs(i, log_tables[i]),
            bg="#2196F3",
            fg="white",
            font=("Arial", 12),
        )
        dropdown_button.grid(row=0, column=2, sticky="e", padx=20)


        # Log table (initially hidden)
        log_table = ttk.Treeview(server_frame, columns=("Timestamp", "Method", "Path"), show="headings", height=5)
        log_table.heading("Timestamp", text="Timestamp")
        log_table.heading("Method", text="Method")
        log_table.heading("Path", text="Path")
        log_table.column("Timestamp", width=200)
        log_table.column("Method", width=100)
        log_table.column("Path", width=200)
        log_table.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=10, pady=10)
        log_table.grid_remove()  # Start hidden
        log_tables.append(log_table)

    # Update server statuses and logs periodically
    update_server_statuses(cards, log_tables)

    root.mainloop()


def add_server():
    """Adds a new server with IP and Port to the backend configuration."""
    server_ip = ip_entry.get()
    server_port = port_entry.get()
    
    if server_ip and server_port:
        try:
            # Ensure the port is a valid integer
            server_port = int(server_port)
            servers.append({"url": f"http://{server_ip}:{server_port}", "connections": 0, "request_count": 0, "status": "Unknown", "logs": []})
            server_list.insert(tk.END, f"{server_ip}:{server_port}")
            ip_entry.delete(0, tk.END)
            port_entry.delete(0, tk.END)
        except ValueError:
            messagebox.showwarning("Warning", "Please enter a valid port number!")
    else:
        messagebox.showwarning("Warning", "Please enter both IP address and port!")


def delete_server():
    """Deletes the selected server from the list and backend configuration."""
    selected_index = server_list.curselection()
    if selected_index:
        selected_server = server_list.get(selected_index)
        server_list.delete(selected_index)
        servers[:] = [server for server in servers if f"{server['url'].split('//')[1]}" != selected_server]
        messagebox.showinfo("Info", f"Server {selected_server} removed successfully!")
    else:
        messagebox.showwarning("Warning", "Please select a server to delete!")


def proceed_to_main_gui():
    """Proceeds to the main GUI if servers are added."""
    if not servers:
        messagebox.showwarning("Warning", "Please add at least one server before proceeding!")
        return
    initial_gui.destroy()
    initialize_main_gui()


# Initial GUI to add servers
initial_gui = tk.Tk()
initial_gui.title("Add Backend Servers")
initial_width = 900
initial_height = 800

# Center the window on the screen
screen_width = initial_gui.winfo_screenwidth()
screen_height = initial_gui.winfo_screenheight()
x_position = (screen_width // 2) - (initial_width // 2)
y_position = (screen_height // 2) - (initial_height // 2)

initial_gui.geometry(f"{initial_width}x{initial_height}+{x_position}+{y_position}")
initial_gui.configure(bg="#f7f7f7")

# Title Label
title_label = tk.Label(
    initial_gui,
    text="Load Balancer Configuration",
    font=("Arial", 20, "bold"),
    bg="#f7f7f7",
    fg="#333"
)
title_label.pack(pady=20)

# Instructions Label
instruction_label = tk.Label(
    initial_gui,
    text="Enter the IP address and port of backend servers and click 'Add'.\nYou can also delete servers before proceeding.",
    font=("Arial", 14),
    bg="#f7f7f7",
    fg="#555"
)
instruction_label.pack(pady=10)

# Input Fields for Server IP and Port
input_frame = tk.Frame(initial_gui, bg="#f7f7f7")
input_frame.pack(pady=10)

ip_label = tk.Label(input_frame, text="IP Address:", font=("Arial", 12), bg="#f7f7f7")
ip_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
ip_entry = tk.Entry(input_frame, font=("Arial", 12), width=30, bd=2, relief="groove")
ip_entry.grid(row=0, column=1, padx=5, pady=5)

port_label = tk.Label(input_frame, text="Port:", font=("Arial", 12), bg="#f7f7f7")
port_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
port_entry = tk.Entry(input_frame, font=("Arial", 12), width=10, bd=2, relief="groove")
port_entry.grid(row=1, column=1, padx=5, pady=5, sticky="w")

# Buttons for Adding and Deleting Servers
button_frame = tk.Frame(initial_gui, bg="#f7f7f7")
button_frame.pack(pady=10)

add_button = tk.Button(
    button_frame,
    text="Add Server",
    command=add_server,
    bg="#4CAF50",
    fg="white",
    font=("Arial", 14),
    width=15
)
add_button.grid(row=0, column=0, padx=10)

delete_button = tk.Button(
    button_frame,
    text="Delete Server",
    command=delete_server,
    bg="#f44336",
    fg="white",
    font=("Arial", 14),
    width=15
)
delete_button.grid(row=0, column=1, padx=10)

# Listbox to Display Added Servers
server_list_frame = tk.Frame(initial_gui, bg="#f7f7f7")
server_list_frame.pack(pady=10)

server_list_label = tk.Label(
    server_list_frame,
    text="Added Servers:",
    font=("Arial", 16),
    bg="#f7f7f7",
    fg="#333"
)
server_list_label.pack(anchor="w")

server_list = tk.Listbox(server_list_frame, font=("Arial", 14), width=60, height=10, bd=2, relief="groove")
server_list.pack(pady=5)

# Proceed Button
proceed_button = tk.Button(
    initial_gui,
    text="Proceed",
    command=proceed_to_main_gui,
    bg="#2196F3",
    fg="white",
    font=("Arial", 16),
    width=20
)
proceed_button.pack(pady=30)

# Run the Initial GUI
initial_gui.mainloop()
Share Improve this question edited Feb 2 at 10:26 Friedrich 4,84613 gold badges53 silver badges49 bronze badges asked Feb 1 at 19:06 Evan BoseEvan Bose 111 bronze badge 2
  • 1. there is no such thing called servers in the module backend. 2. why you iterated for index, server in enumerate(servers):? Did you want to declare servers variable as list? – Suramuthu R Commented Feb 2 at 4:08
  • Yes i want to declare servers variable as list – Evan Bose Commented Feb 2 at 5:42
Add a comment  | 

1 Answer 1

Reset to default 0

You can create a simple scrollable frame using Canvas, Scrollbar and Frame widgets:

# simple vertical scrollable frame widget
class VerticalScrolledFrame(tk.Frame):
    def __init__(self, master=None, **kwargs):
        super().__init__(master)
        self._canvas = tk.Canvas(self, bd=0, highlightthickness=0)
        self._vscrollbar = tk.Scrollbar(self, orient='vertical', command=self._canvas.yview)
        self._canvas.config(yscrollcommand=self._vscrollbar.set)
        self._vscrollbar.pack(side='right', fill='y')
        self._canvas.pack(side='left', fill='both', expand=1)
        self._internal = tk.Frame(self._canvas, **kwargs)
        self._internal_id = self._canvas.create_window(0, 0, window=self._internal, anchor='nw')
        self._internal.bind('<Configure>', self._on_internal_resize)
        self._canvas.bind('<Configure>', self._on_canvas_resize)

    def _on_internal_resize(self, event):
        self._canvas.config(scrollregion=self._canvas.bbox('all'))

    def _on_canvas_resize(self, event):
        self._canvas.itemconfig(self._internal_id, width=event.width)

    @property
    def internal(self):
        return self._internal

Then use this scrollable frame to hold those card layout forms:

def initialize_main_gui():
    ...
    # Frame for card layout
    scrolled_frame = VerticalScrolledFrame(root, bg='#f7f7f7')
    scrolled_frame.pack(pady=20, padx=20, fill="both", expand=True)
    card_frame = scrolled_frame.internal
    ...

Result:

发布评论

评论列表(0)

  1. 暂无评论