2025, Oct 31 03:01
Tkinter में मोडल उप-विंडो: grab_set और wait_window से निष्पादन रोकें
Python Tkinter में उप-विंडो को मोडल बनाकर मुख्य कोड को सही समय तक रोकें। Toplevel, grab_set और wait_window से यूज़र चयन तक सुरक्षित फ्लो पाएं। उदाहरण कोड सहित
जब Tkinter ऐप उपयोगकर्ता इनपुट के लिए एक उप-विंडो खोलता है, तो अक्सर फ़ंक्शन का बाकी हिस्सा तुरंत चलना जारी रहता है। नतीजा परिचित है: पॉप-अप तो दिखता है, लेकिन मुख्य कोड ऐसे आगे बढ़ चुका होता है जैसे उपयोगकर्ता ने पहले ही जवाब दे दिया हो। समाधान सरल है — उप-विंडो को मोडल बनाएं और आगे बढ़ने से पहले उसके बंद होने का स्पष्ट रूप से इंतजार करें।
समस्या का सेटअप
मुख्य विंडो एक उप-विंडो शुरू करती है, जहाँ उपयोगकर्ता एक विकल्प चुनकर Done पर क्लिक करता है। लेकिन उप-विंडो बनते ही मुख्य फ़ंक्शन आगे चल पड़ता है और चयन को पढ़ने की कोशिश करता है, जबकि उपयोगकर्ता ने अभी तक कुछ चुना ही नहीं।
import tkinter as tk
class AppShell:
    def __init__(self, master):
        self.master = master
        self.run_btn = tk.Button(master, text='Run', command=lambda: self.execute_flow(master))
        self.run_btn.grid(row=1, column=0)
    def execute_flow(self, parent):
        stage = 2
        if stage == 2:
            ChoiceDialog(parent)  # उप-विंडो बनाई जाती है, लेकिन कोड इंतजार नहीं करता
        picked = ChoiceDialog.value  # मान बहुत जल्दी पढ़ लिया जाता है
        print(f'selected: {picked}')
root = tk.Tk()
AppShell(root)
root.mainloop()class ChoiceDialog:
    value = ''
    def __init__(self, parent):
        self.win = tk.Toplevel(parent)
        self.win.transient(parent)
        self.win.title('User Input')
        self.listbox = tk.Listbox(self.win, selectmode='browse', exportselection=tk.FALSE, relief='groove')
        self.listbox.grid(row=0, column=0)
        self.items = ['Selection1', 'Selection2', 'Selection3']
        for opt in self.items:
            self.listbox.insert(tk.END, opt)
        tk.Button(self.win, text='Done', command=lambda: self.on_done(self.listbox, self.win)).grid(row=1, column=0)
    def on_done(self, lb, toplevel):
        sel = lb.curselection()
        if len(sel) > 0:
            val = lb.get(sel)
            if val == 'Selection1':
                ChoiceDialog.value = 'Selection1'
            elif val == 'Selection2':
                ChoiceDialog.value = 'Selection2'
            elif val == 'Selection3':
                ChoiceDialog.value = 'Selection3'
            toplevel.destroy()
        else:
            ChoiceDialog.value = 'None'
            toplevel.destroy()
            print('No selection made')असल में क्या होता है और क्यों
उप-विंडो तो बन जाती है, लेकिन मुख्य फ्लो में Tkinter को यह बताने के लिए कुछ नहीं है कि वह उप-विंडो बंद होने तक रुके। इसलिए Tkinter तुरंत अगली पंक्तियों पर चला जाता है और कोड Done पर क्लिक होने से पहले ही चयन पढ़ने की कोशिश करता है। नतीजे में पॉप-अप बाद में दिखता है, जबकि फ़ंक्शन का बाकी हिस्सा पहले ही चल चुका होता है।
समाधान: उप-विंडो को मोडल बनाएं और इंतजार करें
दो कॉल्स से बात बन जाती है। पहले, मुख्य विंडो के साथ इंटरैक्शन रोक दें ताकि उपयोगकर्ता कई उप-विंडो न खोल पाए — उप-विंडो पर grab_set इस्तेमाल करें। फिर, स्पष्ट रूप से उप-विंडो के बंद होने तक प्रतीक्षा करें — उस उप-विंडो के साथ wait_window कॉल करें। इन्हें डायलॉग के initializer के भीतर करना बेहतर है ताकि कॉल साइट साफ-सुथरी रहे।
import tkinter as tk
class AppShellFixed:
    def __init__(self, master):
        self.run_btn.grid(row=1, column=0)
    def execute_flow(self):
        stage = 2
        if stage == 2:
            dlg = ChoiceDialogModal(self.master)  # तब तक ब्लॉक रहता है जब तक बंद न हो जाए
            picked = dlg.selection                 # अब सेट होने की गारंटी है
            print(f'selected: {picked}')
root = tk.Tk()
AppShellFixed(root)
root.mainloop()class ChoiceDialogModal:
    def __init__(self, parent):
        self.top = tk.Toplevel(parent)
        self.selection = None
        self.lb = tk.Listbox(self.top)
        self.lb.grid(row=0, column=0)
        for opt in ['Selection1', 'Selection2', 'Selection3']:
            self.lb.insert(tk.END, opt)
        tk.Button(self.top, text='Done', command=self.handle_done).grid(row=1, column=0)
        self.top.grab_set()                         # मुख्य विंडो तक पहुंच रोकें
        self.top.master.wait_window(self.top)       # उप-विंडो बंद होने तक प्रतीक्षा करें
    def handle_done(self):
        picked = self.lb.curselection()
        if len(picked) > 0:
            self.selection = self.lb.get(picked)
        else:
            self.selection = None
            print('No selection made')
        self.top.destroy()वे बातें जो फ्लो को बेहतर बनाती हैं
उप-विंडो पर प्रतीक्षा करने से मुख्य फ़ंक्शन वहीँ से आगे बढ़ता है, जब उपयोगकर्ता इनपुट पूरा कर देता है। grab_set के कारण इस दौरान उपयोगकर्ता पैरेंट विंडो से इंटरैक्ट नहीं कर सकता, जिससे एक ही डायलॉग दो बार खुलने जैसी समस्या भी नहीं होती। परिणाम को डायलॉग इंस्टेंस पर रखना स्टेट को स्थानीय और आसानी से सुलभ रखता है, और self के जरिए मेथड व विजेट तक पहुँचना उन्हें आगे-पीछे पास करने की जरूरत कम करता है। ChoiceDialogModal जैसे क्लासों के लिए CamelCaseNames अपनाने से पठनीयता बेहतर होती है और यह Tkinter के Frame, Button जैसी कक्षाओं की नामकरण शैली से मेल खाती है। यदि पहले से उपलब्ध इंटरैक्शन आपकी जरूरत पर फिट बैठता है, तो Tkinter के मानक दस्तावेज़ों में दिए Dialog विकल्पों पर भी विचार किया जा सकता है।
यह क्यों महत्वपूर्ण है
GUI कोड में अनुक्रम ही सबकुछ है। यदि स्पष्ट इंतजार न हो, तो उपयोगकर्ता इनपुट पर निर्भर लॉजिक अधूरे स्टेट के साथ चल पड़ता है—ग़लत शाखाएँ, खाली मान, या बार-बार प्रॉम्प्ट जैसी दिक्कतें आती हैं। उप-विंडो को मोडल बनाकर और उसके बंद होने तक प्रतीक्षा करके आप नियंत्रित फ्लो और बेहतर उपयोगकर्ता अनुभव सुनिश्चित करते हैं।
निष्कर्ष
Tkinter उप-विंडो से उपयोगकर्ता का चयन मिलने तक निष्पादन रोकने के लिए, एक Toplevel बनाएं, मुख्य विंडो को ब्लॉक करने हेतु grab_set कॉल करें, और उप-विंडो नष्ट होने तक wait_window के साथ कॉलर को स्थगित रखें। चयन को इंस्टेंस एट्रिब्यूट के रूप में रखें और स्पष्टता के लिए विजेट्स तक self के जरिए पहुँचें। यह पैटर्न सुनिश्चित करता है कि आपका बाकी फ़ंक्शन तभी चले जब उपयोगकर्ता ने चयन कर लिया हो—यानी ठीक उसी समय जब आपको उसकी जरूरत होती है।
यह लेख StackOverflow पर एक प्रश्न (लेखक: Juliette Mitrovich) और furas के उत्तर पर आधारित है।