2025, Oct 31 18:31
Tkinter में स्थिर UI: grid/pack संघर्ष और geometry propagation
स्टार्टअप पर बिखरे UI से छुटकारा पाएं: Tkinter में प्रति कंटेनर एक मैनेजर रखें, grid/pack न मिलाएँ, sticky/fill/expand और pack_propagate से geometry संभालें.
जब Tkinter इंटरफ़ेस स्टार्टअप पर बिखरा-बिखरा दिखे और केवल किसी क्रिया—जैसे कि इमेज अपलोड करने—के बाद अपनी जगह पर आकर सही लगे, तो यह लगभग हमेशा लेआउट प्रबंधन की समस्या का संकेत होता है। इस स्थिति में, इमेज लोड होने से पहले UI लगभग फुल-स्क्रीन तक फैल जाता है और बाद में ही सही आकार में आता है। कारण आम तौर पर यह होता है कि लेआउट मैनेजरों को कैसे मिलाया गया है और विजेट ज्योमेट्री को फैलने (propagate) देने के नियम कैसे तय किए गए हैं।
समस्या का उदाहरण
नीचे दिया गया स्निपेट एक विंडो बनाता है जिसमें एक बटन, कुछ कंट्रोल और एक फ़्रेम है जो लोड हुई इमेज दिखाने के लिए है। इमेज अपलोड होने से पहले UI बिगड़ा हुआ दिखता है और अपलोड के बाद उसका आकार बदल जाता है।
from tkinter import filedialog, ttk
from tkinter import *
from PIL import Image, ImageTk
import numpy as np
# image संदर्भ को कैश करें ताकि यह garbage collected न हो
thumb_ref = None
# इमेज खोलें और लेबल में दिखाएँ
def handle_open():
global thumb_ref
path = filedialog.askopenfilename()
if path:
pic = Image.open(path)
thumb_ref = ImageTk.PhotoImage(pic)
if thumb_ref.width() < 1200 or thumb_ref.height() < 600:
lbl_preview.image = thumb_ref
lbl_preview.config(image=thumb_ref)
# UI बनाएँ
root = Tk()
pane_img = Frame(root)
lbl_preview = Label(pane_img, width=1200, height=600)
btn_upload = Button(root, text='Upload Image', command=handle_open)
lbl_size = Label(root, text="Text size:", font=('calibre',12,'bold'))
cmb_size = ttk.Combobox(root, font=('calibre',12,'normal'), values=list(np.arange(8, 51, step=7)))
lbl_rot = Label(root, text="Rotation:", font=('calibre',12,'bold'))
ent_rot = Entry(root, font=('calibre',12,'normal'))
lbl_op = Label(root, text="Opacity:", font=('calibre',12,'bold'))
sld_op = Scale(root, from_=0, to=100, orient='horizontal')
# स्थान-निर्धारण
pane_img.grid(column=3, row=2, columnspan=5, rowspan=10, padx=50)
lbl_preview.pack()
btn_upload.grid(column=1, row=1, padx=60, pady=60)
lbl_size.grid(column=1, row=2)
cmb_size.grid(row=2, column=2)
lbl_rot.grid(row=3, column=1)
ent_rot.grid(row=3, column=2)
lbl_op.grid(row=4, column=1)
sld_op.grid(row=4, column=2)
root.mainloop()
असल में हो क्या रहा है
दो बातें साथ में असर डाल रही हैं। पहला, एक ही कंटेनर के भीतर अलग-अलग लेआउट मैनेजर मिलाना अनुमति-योग्य नहीं है और टकराव पैदा करता है। भरोसेमंद तरीका है: प्रति कंटेनर एक ही मैनेजर रखें, और जहाँ अलग व्यवहार चाहिए, वहाँ कंटेनरों को नेस्ट करें।
आप एक विजेट के भीतर ऐसे अनेक लेआउट मिला रहे हैं जिन्हें एक साथ उपयोग नहीं किया जा सकता।
दूसरा, सही मैनेजर होने पर भी, विजेट अपने बच्चों के अनुरूप फैल या सिकुड़ सकते हैं, जब तक कि उन्हें रोका न जाए। grid में sticky का, और pack में fill तथा expand का सही इस्तेमाल, साथ ही जहाँ ज़रूरत हो वहाँ size propagation को नियंत्रित करना, ज्योमेट्री को पूर्वानुमेय बनाता है। इमेज क्षेत्र के लिए geometry propagation बंद करने से यह इस बात पर निर्भर कर के नहीं सिकुड़ेगा या फूलेगा कि इमेज मौजूद है या नहीं।
समाधान और सुधरा हुआ उदाहरण
संशोधित संरचना में pack के साथ दो फ़्रेम पास-पास रखे गए हैं। बाएँ फ़्रेम में कंट्रोल के लिए grid उपयोग होती है। दाएँ फ़्रेम में इमेज लेबल है और वहाँ geometry propagation बंद किया गया है, ताकि लेआउट इस बात पर निर्भर न रहे कि इमेज लोड हुई है या नहीं।
from tkinter import filedialog, ttk
from tkinter import *
from PIL import Image, ImageTk
import numpy as np
class UiShell(Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title('Example')
w = self.winfo_screenwidth()
h = self.winfo_screenheight()
self.geometry(f"{w}x{h}")
self.minsize(1200, 600)
self._build_ui()
def _build_ui(self):
self.panel_controls = Frame(self)
self.panel_preview = Frame(self)
# प्रीव्यू पैनल को उसकी सामग्रियों के अनुसार आकार बदलने से रोकें
self.panel_preview.pack_propagate(False)
self.panel_controls.pack(side='left', fill='y', padx=8, pady=8)
self.panel_preview.pack(side='left', fill='both', expand=True)
# दाईं ओर: इमेज क्षेत्र
self.img_holder = Label(self.panel_preview)
self.img_holder.pack(fill='both', expand=True)
# बाईं ओर: कंट्रोल
self.btn_open = Button(self.panel_controls, text='Upload Image', command=self.on_pick_image)
self.lbl_size = Label(self.panel_controls, text="Text size:", font=('calibre',12, 'bold'))
self.cmb_size = ttk.Combobox(self.panel_controls, font=('calibre',12, 'normal'), values=list(np.arange(8, 51, step=7)))
self.lbl_angle = Label(self.panel_controls, text="Rotation:", font=('calibre',12, 'bold'))
self.ent_angle = Entry(self.panel_controls, font=('calibre',12, 'normal'))
self.lbl_opacity = Label(self.panel_controls, text="Opacity:", font=('calibre',12, 'bold'))
self.sld_opacity = Scale(self.panel_controls, from_=0, to=100, orient='horizontal')
self.btn_open.grid(column=0, row=0, sticky='nsew', columnspan=2, pady=8)
self.lbl_size.grid(column=0, row=1, sticky='nsew', pady=4)
self.cmb_size.grid(column=1, row=1, sticky='nsew', pady=4)
self.lbl_angle.grid(column=0, row=2, sticky='nsew', pady=4)
self.ent_angle.grid(column=1, row=2, sticky='nsew', pady=4)
self.lbl_opacity.grid(column=0, row=3, sticky='nsew', pady=4)
self.sld_opacity.grid(column=1, row=3, sticky='nsew', pady=4)
def on_pick_image(self):
fp = filedialog.askopenfilename()
if fp:
im = Image.open(fp)
photo = ImageTk.PhotoImage(im)
if photo.width() < 1200 or photo.height() < 600:
self.img_holder.image = photo
self.img_holder.config(image=photo)
if __name__ == '__main__':
app = UiShell()
app.mainloop()
यह जानना क्यों ज़रूरी है
लेआउट का व्यवहार तय करता है कि आपका Tkinter UI स्थिर दिखेगा या डेटा आते ही अपनी जगहें बदलता लगेगा। यह समझना कि एक ही पैरेंट में pack और grid को नहीं मिलाना चाहिए, और sticky, fill, expand तथा pack_propagate कब उपयोग करने हैं—ऐसी सूक्ष्म बगों को रोकता है जो केवल कुछ रनटाइम परिस्थितियों में सामने आती हैं, जैसे पहली इमेज लोड करना।
समाप्ति
प्रति कंटेनर एक ही लेआउट मैनेजर रखें; जहाँ अलग मैनेजर चाहिए, वहाँ UI को नेस्टेड फ़्रेमों में बाँटें; और जहाँ सामग्री का आकार बदलता रहता है, वहाँ geometry propagation को नियंत्रित करें। इन अभ्यासों के साथ, इमेज लोड होने से पहले और बाद—दोनों ही स्थितियों में—इंटरफ़ेस पूर्वानुमेय रूप से रेंडर होगा, और विंडो का अचानक फैलना या सिकुड़ना आपको नहीं चौंकाएगा।