2025, Sep 30 21:32

Tkinter Canvas पर find_closest के साथ निकटतम ओवल चुनना

Halo और start वास्तव में क्या करते हैं, और ग्रिड लाइनों को बिना छुए Tkinter Canvas पर सही ओवल कैसे चुनें। टैग से आइटम छिपाकर find_closest को सटीक बनाएं।

Tkinter Canvas पर सही ऑब्जेक्ट चुनना जितना लगता है उससे अधिक पेचीदा हो सकता है। जब आप चाहते हैं कि क्लिक सबसे नज़दीकी बिंदु पर स्नैप हों और ग्रिड लाइनों जैसी अन्य आकृतियों को अनदेखा किया जाए, तो find_closest(x, y) को सीधे कॉल करना जल्दी ही अजीब किनारी स्थितियों से टकरा जाता है। यह फ़ंक्शन डिस्प्ले स्पेस में जो भी सबसे पास होता है, उसे खुशी-खुशी लौटा देता है—जो अक्सर उस छोटे से ओवल के बजाय कोई लाइन सेगमेंट निकल आता है जिस पर आपका ध्यान है। start और halo के साथ खोज को सीमित करने की कोशिशें भी अक्सर उम्मीद के मुताबिक काम नहीं करतीं।

समस्या को पुनः उत्पन्न करना

नीचे दिया गया उदाहरण दो 3×3 समूहों में छोटे ओवल और एक साधारण ग्रिड बनाता है। कैनवास पर क्लिक करने से सबसे पास वाला ओवल चिन्हित होना चाहिए, लेकिन start के साथ की गई कॉल फिर भी किसी ग्रिड लाइन को लौटा सकती है।

import tkinter as tk
from tkinter import ttk
class PlotPad:
    def __init__(self, master):
        self.edge = 600
        self.master = master
        self.shell = ttk.Frame(self.master)
        self.board = tk.Canvas(self.shell, width=self.edge, height=self.edge)
        self.board.bind("<Motion>", self.on_move)
        self.board.bind("<Button-1>", self.on_click)
        self.footer = ttk.Frame(self.shell)
        self.txt = ttk.Label(self.footer, relief=tk.GROOVE, text='Coordinates go here')
        self.paint_dots(-150, 'blue', 'first_batch')
        self.paint_dots(-20, 'black', 'second_batch')
        self.paint_grid()
        self.shell.pack()
        self.board.pack()
        self.footer.pack(fill='both', expand=True)
        self.txt.pack(side='left', fill='x', expand=True)
    def on_move(self, event):
        px, py = event.x, event.y
        self.txt.config(text=f"Mouse coordinates: ({px}, {py})")
    def on_click(self, event):
        px, py = event.x, event.y
        # start के साथ सीमा लगाकर अंडाकारों को प्राथमिकता देने का प्रयास
        nearest = self.board.find_closest(px, py, halo=0, start=9)
        bbox = self.board.coords(nearest[0])
        cx, cy = (bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2
        span = 5
        self.board.create_line(cx - span, cy - span, cx + span, cy + span, fill='red')
        self.board.create_line(cx - span, cy + span, cx + span, cy - span, fill='red')
    def paint_dots(self, offset, color, tagname):
        for ix in range(1, 4):
            for iy in range(1, 4):
                self.board.create_oval(
                    ix * 200 + offset - 1,
                    iy * 200 + offset - 1,
                    ix * 200 + offset + 1,
                    iy * 200 + offset + 1,
                    fill=color,
                    tags=tagname
                )
    def paint_grid(self):
        for ix in range(1, 4):
            self.board.create_line(
                ix * 200 + 10,
                0,
                ix * 200 + 10,
                600,
                fill='green',
                tags='grid_lines')
        for iy in range(1, 4):
            self.board.create_line(
                0,
                iy * 200 + 10,
                600,
                iy * 200 + 10,
                fill='green',
                tags='grid_lines')
if __name__ == '__main__':
    root = tk.Tk()
    app = PlotPad(root)
    root.mainloop()

वास्तव में क्या हो रहा है

Canvas.find_closest के दो आर्ग्युमेंट ऐसे लगते हैं कि वे काम आएंगे, लेकिन वे इस स्थिति का समाधान नहीं करते। halo पैरामीटर क्लिक बिंदु को दिए गए त्रिज्या के एक वृत्त में फैलाता है, यानी माउस लोकेशन के आसपास हिट एरिया बढ़ा देता है। यह ऑब्जेक्ट के प्रकारों को फ़िल्टर नहीं करता। start पैरामीटर खोज को किसी ID रेंज या टैग तक सीमित नहीं करता; बल्कि, जब कई ऑब्जेक्ट बिल्कुल समान निकटतम स्थान पर हों, तो यह डिस्प्ले लिस्ट में start से नीचे वाले को चुनता है। यानी यह z-ऑर्डर में टाई तोड़ने के लिए उपयोगी है, न कि ग्रिड लाइनों जैसी संपूर्ण श्रेणियों को बाहर रखने के लिए।

इसी कारण, यदि कोई लाइन क्लिक बिंदु के किसी छोटे ओवल से अधिक पास है, तो find_closest अब भी उसी लाइन को लौटा सकता है।

व्यावहारिक उपाय: जिन पर हिट नहीं करना चाहते उन्हें अस्थायी रूप से छिपाएँ

एक सीधा समाधान यह है कि जिन आकृतियों को अनदेखा करना है, उनका state अस्थायी रूप से बदल दें, चयन कर लें, और फिर उन्हें वापस दिखा दें। चूँकि सभी ग्रिड लाइनों का टैग एक ही है, इसलिए यह एक सरल और सटीक बदलाव बन जाता है। find_closest कॉल करने से पहले 'grid_lines' को छिपाएँ और बाद में उन्हें पुनः सामान्य कर दें।

def on_click(self, event):
    px, py = event.x, event.y
    # हिट टेस्टिंग से ग्रिड लाइनों को अस्थायी रूप से हटाएँ
    self.board.itemconfig('grid_lines', state="hidden")
    nearest = self.board.find_closest(px, py)
    self.board.itemconfig('grid_lines', state="normal")
    bbox = self.board.coords(nearest[0])
    cx, cy = (bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2
    span = 5
    self.board.create_line(cx - span, cy - span, cx + span, cy + span, fill='red')
    self.board.create_line(cx - span, cy + span, cx + span, cy - span, fill='red')

यह किसी ज्यामितीय फेरबदल या आइटम्स को कैनवास से बाहर भेजने की आवश्यकता नहीं छोड़ता। यह हिट टेस्ट के दौरान जिन ऑब्जेक्ट्स को दबाना है उन्हें अलग करने के लिए एक सुसंगत टैग पर निर्भर करता है—जो वैसे भी Canvas पर इंटरएक्टिव लेयर्स प्रबंधित करने का सामान्य तरीका है।

यह क्यों मायने रखता है

मैप एडिटरों और डायग्रामिंग टूल्स से लेकर हल्के CAD-जैसे इंटरैक्शन तक, कई UI व्यवहारों के केंद्र में पॉइंटर पिकिंग होती है। यह समझना कि halo क्लिक क्षेत्र को बड़ा करता है और start डिस्प्ले ऑर्डर में टाई-ब्रेकिंग को प्रभावित करता है, तब काम आता है जब आपको चयन को आइटमों के एक उपसमुच्चय तक सीमित करना हो—और बेकार रास्तों से बचाता है। जब फ़िल्टरिंग चाहिए, तो टैग के आधार पर अप्रासंगिक तत्वों को अस्थायी रूप से छिपाना एक भरोसेमंद पैटर्न है।

मुख्य बातें

यदि आपको find_closest को Canvas आइटमों के किसी उपसमुच्चय पर ही चलाना है, तो start या halo से ऑब्जेक्ट वर्गों के फ़िल्टर होने की उम्मीद न रखें। साफ-सुथरी टैगिंग का उपयोग करें और जिन लेयर्स को बाहर रखना है उन्हें क्षणिक रूप से छिपाएँ, फिर चयन होते ही तुरंत बहाल कर दें। यदि बिना छिपाए ही खोज को विशिष्ट आइटमों तक सीमित करना आवश्यक हो, तो एक और तरीका यह है कि लक्षित टैग वाले आइटमों पर इटरनेट करें और दूरी को मैन्युअल रूप से निकालें; लेकिन जहाँ टैग अच्छी तरह संरचित हों, वहाँ स्टेट-टॉगलिंग संक्षिप्त और प्रभावी है।