Почему мой холст Tkinter не позволяет мне прокручивать полностью вниз после создания новых виджетов?Python

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Почему мой холст Tkinter не позволяет мне прокручивать полностью вниз после создания новых виджетов?

Сообщение Anonymous »

Код: Выделить всё

import tkinter as tk
from tkinter import *
from tkinter import ttk
from datetime import datetime
import ttkbootstrap as ttk
from ttkbootstrap import window
import pandas as pd
import calendar
import random
from tkinter import font
import tkinter.messagebox as tkmb

root = ttk.Window(themename="darkly")
root.title("shifts_scheduler")
w_int = root.winfo_screenwidth()
h_int = root.winfo_screenheight()

root.geometry(f"{w_int}x{h_int}")
root.update_idletasks()
root.resizable(True, True)

# Create a canvas and a vertical scrollbar
canvas = ttk.Canvas(root, width=root.winfo_screenwidth(), height=root.winfo_screenheight())

# Create a frame inside the canvas
master_window = ttk.Frame(master=canvas, )
master_window.bind("", lambda e: on_frame_configure(canvas))

# Pack the frame into the canvas and center it horizontally
canvas.create_window((root.winfo_screenwidth() // 2, 0), window=master_window, anchor="n")
canvas.grid(row=1, column=1, rowspan=6, padx=0, pady=0)
scrollbar = ttk.Scrollbar(master=root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.grid(column=1, row=0, sticky="ne")
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
canvas.bind("", lambda e:  on_frame_configure(canvas))

# Track the entries in a dictionary keyed by the row number
max_entries = {}

worker_entries = []
workers_data = []
workers_list = []

def on_frame_configure(canvas):
# Reset the scroll region to encompass the inner frame
canvas.configure(scrollregion=canvas.bbox("all"))

def max_box(mvar, row):
if mvar.get() == 1:
# Create a new Entry widget for the given row
max_entry = ttk.Entry(master=generated_frame, font="Calibri 14")
max_entry.grid(row=row, column=7, padx=10, pady=10)
max_entries[row] = max_entry  # Store it in the dictionary
else:
# Destroy the entry if it exists and remove it from the dictionary
if row in max_entries:
max_entries[row].destroy()
del max_entries[row]

def is_weekend(date_str):
date_obj = datetime.strptime(date_str, '%d %m %Y')
day_num = date_obj.weekday()  # 0 for Monday, 1 for Tuesday, ..., 6 for Sunday
return day_num >= 5  # Returns True if it's Saturday or Sunday, indicating a weekend

def day_of_week(date_str):
date_obj = datetime.strptime(date_str, '%d %m %Y')
day_num = date_obj.weekday()  # 0 for Monday, 1 for Tuesday, ..., 6 for Sunday
return day_num

def get_days_in_month(year_int, month):
return calendar.monthrange(year_int, month)[1]

def generate_workers():
global worker_entries  # Declare global at the start of the function
number_workers = workers_int.get()

# Destroy all previous widgets in the generated frame
for widget in generated_frame.winfo_children():
widget.destroy()

# Reset the max_entries dictionary and worker names list
max_entries.clear()
worker_entries.clear()  # Clear the list to avoid old entries
workers_data.clear()
workers_list.clear()  # Clear the worker data list
# Clear the worker data list

# Generate labels and entries for each worker
tested_label = ttk.Label(master=generated_frame, text="✔", font="Calibri 21")
untested_label = ttk.Label(master=generated_frame, text="❌", font="Calibri 15")
name_label = ttk.Label(master=generated_frame, text="Name", font="Calibri 24")
unavilable_label = ttk.Label(master=generated_frame, text="Unavailable", font="Calibri 24")
avilable_label = ttk.Label(master=generated_frame, text="Available", font="Calibri 24")
maxs_label = ttk.Label(master=generated_frame, text="Max", font="Calibri 24")

tested_label.grid(row=0, padx=10, pady=10, column=1)
untested_label.grid(row=0, padx=10, pady=10, column=2)
name_label.grid(row=0, padx=10, pady=10, column=3)
unavilable_label.grid(row=0, padx=10, pady=10, column=4)
avilable_label.grid(row=0, padx=10, pady=10, column=5)
maxs_label.grid(row=0, padx=10, pady=10, column=6)

for i in range(number_workers):
y_var = tk.IntVar()
n_var = tk.IntVar()
m_var = tk.IntVar()

tested_box = tk.Checkbutton(master=generated_frame, variable=y_var, onvalue=1, offvalue=0)
untested_box = tk.Checkbutton(master=generated_frame, variable=n_var,onvalue=1, offvalue=0)
workers_name = ttk.Entry(master=generated_frame, font="Calibri 14")
unavailable_input = ttk.Entry(master=generated_frame, font="Calibri 14")
available_input = ttk.Entry(master=generated_frame, font="Calibri 14")
maxs_box = tk.Checkbutton(master=generated_frame, variable=m_var, command=lambda mvar=m_var, row=i+1: max_box(mvar, row), onvalue=1, offvalue=0)

tested_box.grid(row=i+1, pady=10, padx=10, column=1)
untested_box.grid(row=i+1, pady=10, padx=10, column=2)
workers_name.grid(row=i+1, pady=10, padx=10, column=3)
unavailable_input.grid(row=i+1, pady=10, padx=10, column=4)
available_input.grid(row=i+1, pady=10, padx=10, column=5)
maxs_box.grid(row=i+1, pady=10, padx=10, column=6)

# Add the worker details to the appropriate lists
workers_list.append({
"name": workers_name,
"available": available_input,
"unavailable": unavailable_input,
"max": max_entries,
"tested": y_var,
"untested":  n_var
})
canvas.update_idletasks()

def generate_schedule():
output_window.delete("1.0", "end")
global workers_list
month = month_int.get()
if month > 12 or month < 1:
tkmb.showerror("Error:", f"Month must be beetwen 1 - 12.")
return
days_in_month = get_days_in_month(year_int, month)

worker_info = {}
full_day = {i: None for i in range(1, days_in_month + 1)}  # Initialize the schedule for each day
stand_day = {i: None for i in range(1, days_in_month + 1)}  # Standby worker assignments

# Gather worker data and store it in worker_info
for i, worker in enumerate(workers_list):
name = worker["name"].get()
available = worker["available"].get()
unavailable = worker["unavailable"].get()

# Get the status from the checkboxes (assuming both checkboxes exist and are mutually exclusive)
tested = worker["tested"].get()  # Checkbox for 'tested'
untested = worker["untested"].get()  # Checkbox for 'untested'

# Determine if the worker is tested or untested
if tested:
is_tested = True
elif untested:
is_tested = False
else:
tkmb.showerror("Error:", f"Error: Worker '{name}' must be marked as either tested or untested.\n", )
return
# Skip this worker if no valid status is selected

# Convert strings to lists of integers
available_days = [int(d.strip()) for d in available.split(",") if d.strip().isdigit()] if available else []
unavailable_days = [int(d.strip()) for d in unavailable.split(",") if d.strip().isdigit()] if unavailable else []

# Check for conflicting entries between available and unavailable days
conflicting_days = set(available_days).intersection(unavailable_days)
if conflicting_days:
tkmb.showerror("Error", f"Error: Worker '{name}' has conflicting days in available and unavailable inputs: {conflicting_days}\n")
return  # Skip this worker in case of an error

# Get the maximum shifts if provided
max_shifts = max_entries.get(i + 1)
max_value = int(max_shifts.get()) if max_shifts and max_shifts.get().isdigit() else None

worker_info[i] = {
"name": name,
"available_days": available_days,
"unavailable_days": unavailable_days,
"tested": is_tested,  # Store whether the worker is tested or not
"max": max_value,
"count": 0,
"shifts": 0,
"standby": 0  # Initial shift count
}

# Step 1: Assign workers to available days first (shift assignments)
for day in range(1, days_in_month + 1):
available_workers = [
worker for worker in worker_info.values()
if worker["available_days"] and day in worker["available_days"] and worker["name"] != stand_day[day] and (worker["max"] is None or worker["count"] < worker["max"])
]
if available_workers:
# Sort available workers by shifts and standbys (lowest first)
available_workers.sort(key=lambda w: (w["shifts"]))
selected_worker = available_workers[0]  # Choose the worker with the fewest shifts/standbys
full_day[day] = selected_worker["name"]
selected_worker["shifts"] += 1
selected_worker["count"] += 1

for day in range(1, days_in_month + 1):
if full_day[day] is None:  # No worker assigned
available_workers = [
worker for worker in worker_info.values()
if not worker["available_days"] and day not in worker["unavailable_days"] and worker["name"] != stand_day[day] and (worker["max"] is None or worker["count"] <  worker["max"])
]
if available_workers:
available_workers.sort(key=lambda w: (w["shifts"]))
selected_worker = available_workers[0]  # Choose the worker with the fewest shifts
full_day[day] = selected_worker["name"]
selected_worker["shifts"] += 1
selected_worker["count"] += 1

# Step 2: Assign workers to standby roles on available days
for day in range(1, days_in_month + 1):
if not stand_day[day]:  # If no standby assigned
shift_worker = full_day[day]
shift_worker_info = next((worker for worker in worker_info.values() if worker["name"] == shift_worker), None)

available_workers = [
worker for worker in worker_info.values()
if worker["available_days"] and day in worker["available_days"] and worker["name"] != full_day[day]
]

# Check if the shift worker is untested, and filter available workers accordingly
if shift_worker_info and not shift_worker_info["tested"]:
available_workers = [worker for worker in available_workers if worker["tested"]]

if available_workers:
# Sort available workers by shifts and standbys (lowest first)
available_workers.sort(key=lambda w: (w["standby"], w["shifts"]))
selected_worker = available_workers[0]  # Choose the worker with the fewest standbys
stand_day[day] = selected_worker["name"]
selected_worker["standby"] += 1
selected_worker["count"] += 1

# Step 3: Assign standby workers for unassigned days
for day in range(1, days_in_month + 1):
if stand_day[day] is None:
shift_worker = full_day[day]
shift_worker_info = next((worker for worker in worker_info.values() if worker["name"] == shift_worker), None)

available_workers = [
worker for worker in worker_info.values()
if not worker["available_days"] and day not in worker["unavailable_days"] and worker["name"] != full_day[day]
]

# Check if the shift worker is untested, and filter available workers accordingly
if shift_worker_info and not shift_worker_info["tested"]:
available_workers = [worker for worker in available_workers if worker["tested"]]

if available_workers:
available_workers.sort(key=lambda w: (w["standby"], w["shifts"]))
selected_worker = available_workers[0]
stand_day[day] = selected_worker["name"]
selected_worker["standby"] += 1
selected_worker["count"] += 1

for day in range(1, days_in_month + 1):
fday = format(day, '02d')

date_str = f'{fday} {month} {year_int}'
weekday_num = day_of_week(date_str)

if is_weekend(date_str):
output_window.insert("end", f"{fday}-{month}-{year_int} - Shift: {full_day[day]} - Standby: {stand_day[day]}\n")
output_window.tag_add(f"day{day}", f"{day}.0", f"{day}.end")
output_window.tag_configure(f"day{day}", font=("Calibri", 18, "bold"))
else:
output_window.insert("end", f"{fday}-{month}-{year_int} - Shift: {full_day[day]} - Standby: {stand_day[day]}\n")
output_window.tag_add(f"day{day}", f"{day}.0", f"{day}.end")
output_window.tag_configure(f"day{day}", font=("Calibri", 14))

for worker in worker_info.values():
output_window.insert("end",f"Worker: {worker['name']} - Shifts: {worker['shifts']} - Standby:  {worker['standby']}\n")
canvas.update_idletasks()

def load():
pass

def save():
pass

def export():
pass

def copy():
master_window.clipboard_clear()  # Optional.
master_window.clipboard_append(output_window.get('1.0', tk.END).rstrip())

# Input fields
input_frame = ttk.Frame(master=master_window)
month_int = tk.IntVar()
month_label = ttk.Label(master=input_frame, text="Month:", font="Calibri 24")
month_entry = ttk.Entry(master=input_frame, font="Calibri 14", textvariable=month_int)

year_int = datetime.now().year
year_label = ttk.Label(master=input_frame, text="Year:", font="Calibri 24")
year_entry = ttk.Entry(master=input_frame, font="Calibri 14")
year_entry.insert(0, str(year_int))

save_buton = ttk.Button(master=input_frame, text="Save", command=save)
load_buton = ttk.Button(master=input_frame, text="Load", command=load)

input_frame.pack(pady=10)
load_buton.pack(side="left", padx=10)
save_buton.pack(side="left", padx=10)
year_label.pack(side="left", padx=10)
year_entry.pack(side="left")
month_label.pack(side="left", padx=10)
month_entry.pack(side="left")

# Generate worker fields
workers_frame = ttk.Frame(master=master_window)
workers_int = ttk.IntVar()
workers_label = ttk.Label(master=workers_frame, text="Workers:", font="Calibri 14")
workers_entry = ttk.Entry(master=workers_frame, font="Calibri 14", textvariable=workers_int)
workers_button = ttk.Button(master=workers_frame, text="Generate", command=generate_workers)

workers_frame.pack(pady=10)
workers_label.pack(side="left", padx=10)
workers_entry.pack(side="left")
workers_button.pack(side="left", padx=10)

# Frame to hold generated fields
generated_frame = ttk.Frame(master=master_window)
generated_frame.pack(pady=10)

# Output frame with centered elements
def on_output_window_mousewheel(event):
output_window.yview_scroll(int(-1 * (event.delta / 120)), "units")
return "break"  # Prevents the event from propagating to other widgets

def on_canvas_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

# Create output_frame and related widgets
output_frame = ttk.Frame(master=master_window)
output_button = ttk.Button(master=output_frame, text="Generate Schedule", command=generate_schedule)
output_window = ttk.Text(master=output_frame, width=int(w_int / 16))
exporttoexcel_button = ttk.Button(master=output_frame, text="Export to excel", command=export)
copy_button = ttk.Button(master=output_frame, text="Copy to clipboard", command=copy)

# Pack the widgets
output_frame.pack(pady=10)
output_button.pack(pady=10)
output_window.pack()
exporttoexcel_button.pack(side="left", pady=10)
copy_button.pack(side="left", pady=10, padx=10)

# Bind mouse wheel event to the output_window for internal scrolling
output_window.bind("", on_output_window_mousewheel)

# Bind mouse wheel event to the canvas for overall scrolling
canvas.bind_all("", on_canvas_mousewheel)

root.mainloop()

Когда я запускаю код Tkinter, начальное окно загружается как положено, но я сталкиваюсь с несколькими проблемами, связанными с прокруткой, когда динамически генерирую новые виджеты.
  • Проблема с прокруткой: после создания некоторых виджетов я не могу прокрутить их до конца вниз, чтобы просмотреть вновь добавленный контент. Кажется, что прокрутка ограничена определенной высотой, не позволяя мне достичь нижней части окна. Такое поведение сохраняется, хотя я ожидаю, что область прокрутки будет расширяться по мере создания новых виджетов.
  • Немедленная прокрутка вверх: сразу после запуска программы Я могу прокручивать вверх, даже если над видимым окном нет содержимого. В идеале я не должен иметь возможности прокручивать вверх до тех пор, пока не будет создан новый контент, превышающий высоту окна.
  • Позиционирование полосы прокрутки: Полоса прокрутки не появляется в правой части окна там, где он должен быть. Вместо этого полоса прокрутки кажется смещенной, что делает пользовательский интерфейс менее интуитивным в использовании.
Я уже пробовал такие решения, как настройка область прокрутки динамически, но эти проблемы сохраняются. Любые предложения по обеспечению правильного поведения прокрутки как вверх, так и вниз, а также по исправлению положения полосы прокрутки.

Подробнее здесь: https://stackoverflow.com/questions/790 ... ter-genera
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»