Widgetbeladenne Fensterfläche anklicken

Fragen zu Tkinter.
Antworten
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Ich möchte jedesmal dann ein Event aktivieren, wenn ich mit der Maus auf eine Teilfläche meines Fensters klicke, die durch einen Frame begrenzt ist.

Rein theoretisch könnte ich den definierenden Frame an das Mausevent binden, aber praktisch funktioniert das nicht, weil dieser Frame Labels und andere Kind-Widgets enthält, und es dann nicht zur Kenntnis nimmt, wenn ich zufälligerweise auf eines dieser Kindwidgets klicke. So etwas ist *nicht erwünscht*: das Event soll immer aktiviert werden, egal ob ich auf den Frame oder irgendeines der Kind- oder Enkelwidgets klicke.

Wenn ich zuoberst auf sämtliche Kindevents ein transparentes Label oder Canvas mit den Dimensionen des Frames legen könnte, dann könnte ich dieses transparente Label oder Canvas an das Event binden. Aber das funktioniert ja schon deshalb nicht, weil (1)_es in tkinter anscheinend keine transparenten Widgets gibt, und (2)_ich nicht wüsste, was ich als Elternwidget für das transparente Widget nehmen kann, ohne die Kindwidgets von ihrem Ort zu verdrängen.

Gibt es dafür eine Lösung? Oder besteht die beste Lösung darin, tkinter zu vergessen und ein anderes GUI-Tool zu benutzen?
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Goswin

Was hältst du von dieser Lösung?:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk

def event_callback(event):
   if str(main_frame) in str(event.widget):
        print('Bingo! MainFrame-Event')
        
app_win = tk.Tk()
app_win.geometry('300x300')
app_win.bind('<Button-1>', event_callback)

main_frame = tk.Frame(app_win, width=200, height=200, bg='yellow')
main_frame.propagate(False)
main_frame.pack(expand=True)

sub_frame = tk.Frame(main_frame, width=100, height=100, bg='green')
sub_frame.propagate(False)
sub_frame.pack(expand=True)

canvas = tk.Canvas(sub_frame, width=50, height=50, bg='blue')
canvas.pack(expand=True)

app_win.mainloop()
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@wuf:

Vielen Dank, deine Lösung funktioniert offenbar - zumindest auf meinem Rechner! :)

Ich weiß aber immer noch nicht, wo nun der feine Unterschied zwischen einem Tk-Objekt, Toplevel-Objekt, und einem beliebigen Frame besteht, mein Python-Handbuch ist veraltet und sagt nichts darüber. Reagiert das Tk-Objekt auf sämtliche Events, die für irgendein Widget stattfinden? Gilt das für beliebige Toplevel-Objekte?

Und nicht zuletzt: Ist die verwendete str-Darstellung der Subframes und Widgets Standard oder implementierungsabhängig? Wird sie irgendwo offiziell beschrieben?
BlackJack

@Goswin: Das Tkinter-Buch vom Effbot ist immer wieder hilfreich: http://effbot.org/tkinterbook/tkinter-e ... ndings.htm

Da stehen die vier Ebenen für die man `bind()` verwenden kann beschrieben und in welcher Reihenfolge die aufgerufen werden.

Die Zeichenkettendarstellung eines Widget-Objekts ist laut Docstring der Tk-Pfadname. Widgets sind in Tk in einer Hierarchie angeordnet mit '.' als Wurzel/Pfadtrenner. Die Tk-Namen für die Widgets würfelt Tkinter irgendwie aus aber ansonsten ist das Format schon (Tk-)Standard.

Tkinter ist ja nur eine *sehr* dünne Schicht über dem Tk-Interpreter. Oft hilft es sich in Tk-Dokumentation einzulesen wenn man mehr Informationen benötigt als die Tkinter-Dokumentation hergibt.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@wuf:
Für das was ich anstrebe, funktioniert deine Lösung leider doch nicht oder ich verstehe sie nicht richtig.

Genauer gesagt: sie funktioniert anscheinend nicht, wenn ich zwischen mehrere verschiedenen Frames unterscheiden muss; im unteren Beispiel ordnet sie das ButtonRelease-Event demselben Frame zu wie das letztaktivierte Button-Event, obwohl ich die Maustaste über einem anderen Frame loslasse:

Code: Alles auswählen

#!/usr/bin/python3
#coding=utf8


import tkinter as tk
def main():

   def click_callback(event):
      print(str(mainframe1),str(mainframe2),str(event.widget))
      if str(mainframe1) in str(event.widget):
         print(str(mainframe1),str(event.widget))
         print('MainFrame1-Click')
      if str(mainframe2) in str(event.widget):
         print('MainFrame2-Click')

   def release_callback(event):
      print(str(mainframe1),str(mainframe2),str(event.widget))
      if str(mainframe1) in str(event.widget):
         print('MainFrame1-Release')
      if str(mainframe2) in str(event.widget):
         print('MainFrame2-Release')


   app_win = tk.Tk()
   app_win.geometry('600x300')
   app_win.resizable(False,False)
   app_win.bind('<Button-1>', click_callback)
   app_win.bind('<ButtonRelease-1>', release_callback)
 
   mainframe1 = tk.Frame(app_win, width=200, height=200, bg='cyan')
   mainframe1.propagate(False)
   mainframe1.pack(expand=True,side=tk.LEFT)
   #
   subframe1 = tk.Frame(mainframe1, width=100, height=100, bg='green')
   subframe1.propagate(False)
   subframe1.pack(expand=True)


   mainframe2 = tk.Frame(app_win, width=200, height=200, bg='yellow')
   mainframe2.propagate(False)
   mainframe2.pack(expand=True,side=tk.LEFT)
   #
   subframe2 = tk.Frame(mainframe2, width=100, height=100, bg='red')
   subframe2.propagate(False)
   subframe2.pack(expand=True)

 
   app_win.mainloop()
if __name__ == '__main__': main()
Auf welches Widget wird 'event.widget' beim Aufreten von 'event' gesetzt? Offenbar nicht auf das Widget, wo das Event 'event' stattfindet! :(
BlackJack

@Goswin: Alle Mausereignisse zwischen 'ButtonPress' und 'ButtonRelease' (inklusive) werden an das gleiche Widget gesendet welches das 'ButtonRelease'-Ereignis empfangen hat. Steht auch so beim Effbot dokumentiert. Du könntest Dir bei der Behandlung vom 'ButtonRelease' die `x_root`- und `y_root`-Attribute vom Ereignisobjekt nehmen und mit `Widget.winfo_containing()` das Widget an dieser Position geben lassen. Ungetestet.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Goswin

Dank dem Tipp von BlackJack habe ich noch folgendes ausprobiert:

Code: Alles auswählen

#!/usr/bin/python3
#coding=utf8
 
 
import tkinter as tk
def main():
 
   def click_callback(event):
      event_path = event.widget.winfo_containing(event.x_root, event.y_root)
      if str(mainframe1) in str(event_path):
         print(str(mainframe1),str(event.widget))
         print('MainFrame1-Click')
      if str(mainframe2) in str(event_path):
         print(str(mainframe2),str(event.widget))
         print('MainFrame2-Click')
 
   def release_callback(event):
      event_path = event.widget.winfo_containing(event.x_root, event.y_root)
      print(event_path)
      if str(mainframe1) in str(event_path):
         print(str(mainframe1),str(event.widget))
         print('MainFrame1-Release')
      if str(mainframe2) in str(event_path):
         print(str(mainframe2),str(event.widget))
         print('MainFrame2-Release')
        
   app_win = tk.Tk()
   app_win.geometry('600x300')
   app_win.resizable(False,False)
   app_win.bind('<Button-1>', click_callback)
   app_win.bind('<ButtonRelease-1>', release_callback)
 
   mainframe1 = tk.Frame(app_win, width=200, height=200, bg='cyan')
   mainframe1.propagate(False)
   mainframe1.pack(expand=True,side=tk.LEFT)
   #
   subframe1 = tk.Frame(mainframe1, width=100, height=100, bg='green')
   subframe1.propagate(False)
   subframe1.pack(expand=True)
 
 
   mainframe2 = tk.Frame(app_win, width=200, height=200, bg='yellow')
   mainframe2.propagate(False)
   mainframe2.pack(expand=True,side=tk.LEFT)
   #
   subframe2 = tk.Frame(mainframe2, width=100, height=100, bg='red')
   subframe2.propagate(False)
   subframe2.pack(expand=True)
 
 
   app_win.mainloop()
if __name__ == '__main__': main()
Gruss wuf :wink:
Take it easy Mates!
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Danke euch beiden! Ich benutze das jetzt einmal so wie es ist und verschiebe das Verstehen auf später. :mrgreen:
Antworten