Dem Unterfenster immer den Focus geben, solange es geöffnet ist

Fragen zu Tkinter.
Antworten
Fritz_60
User
Beiträge: 3
Registriert: Montag 31. Januar 2022, 13:48

Hallo an alle,

ich versuche mich in die Programmiersprache Python einzuarbeiten und habe mit einem kleinen Projekt begonnen.
Das Projekt besteht aus einem Hauptfenster. Durch Unterfenster soll man dann später Werte eingeben können.

Nun zu meiner Frage:
Wenn ich z.B. mit der linken Maustaste in das Hauptfenster klicke, bekommt ja das Hauptfenster den Focus.
Wie kann ich den Focus wieder automatisch auf das Unterfenster setzen, sobald die Maustaste nicht mehr gedrückt ist?

Am besten kann man es beim Dialogfenster "Datei öffnen" beobachten, nur mal so als Randbemerkung.

Code: Alles auswählen

import tkinter as tk
from tkinter import filedialog

def option1():
    opt1 = tk.Toplevel()
    opt1.title("Einstellungen")
    opt1.geometry('500x500')
    opt1.resizable(0, 0)
    opt1.attributes('-topmost', True)
    opt1.grab_set()
    opt1.focus_set()


menuleiste = tk.Menu(fenster)

menue1 = tk.Menu(menuleiste, tearoff=0)
menue2 = tk.Menu(menuleiste, tearoff=0)

menuleiste.add_cascade(label="Datei", menu=menue1)
menuleiste.add_cascade(label="Extras", menu=menue2)

menue1.add_command(label="Neu")
menue2.add_command(label="Einstellungen", command=option1)



fenster = tk.Tk()
fenster.title("Hauptfenster")
fenster.geometry('1000x800') 

fenster.config(menu=menuleiste)
fenster.mainloop()

Gruß Fritz
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das sollte mit grab_set gehen, siehe https://stackoverflow.com/questions/451 ... ter-window
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fritz_60: In `tkinter.simpledialog` gibt es `Dialog` als Basisklasse für Dialogfenster.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo Fritz,

wenn die Maustaste losgelassen wird, muss ein Event den Focus wieder an das Unterfenster zurückgeben. Ich hab dir noch ein Entrywidget eingebaut, damit Du siehst ob der Focus wirklich da ist.
Dein Code hat bei mir nicht funktioniert. Die Menueleiste wird vor dem root - Fenster erzeugt, was einen Fehler wirft. Das Unterfenster kann auch schon von Beginn weg erzeugt und versteckt (withdraw) werden. Mit deiconify wird es wieder angezeigt.
Übrigens mit grab verhindert Du die Bedienung der Menueleiste. Ob das so gewollt ist?
Ich hab noch das Schliessen von opt1 behandelt, so dass keine Ausnahme geworfen wird. Code soll in Funktionen, nicht auf Modulebene stehen.

Code: Alles auswählen

import tkinter as tk
from functools import partial

def option1(opt1):
    opt1.deiconify() # Zaubert das versteckte Fenster wieder hervor   

def ruecksetze_focus(root, opt1, entry, event):
    opt1.transient(root) # opt1 sichtbar auf root
    entry.focus_set()

def beende_opt1(root, opt1, menue2):    
    root.unbind("<FocusIn>")
    menue2.entryconfigure(1, state='disabled')
    #Untermenue Einsteklungen wird unwirksam
    opt1.destroy() # Zerstört opt1    

def main():    
    root = tk.Tk()
    opt1 = tk.Toplevel()
    opt1.title("Einstellungen")
    opt1.geometry('500x500')
    opt1.resizable(0, 0)
    opt1.attributes('-topmost', True)    
    opt1.withdraw() # Versteckt opt1 in den Tiefendes Systems  

    entry = tk.Entry(opt1)
    entry.pack()
    
    menuleiste = tk.Menu(root)
    menue1 = tk.Menu(menuleiste, tearoff=0)
    menue2 = tk.Menu(menuleiste, tearoff=0)

    menuleiste.add_cascade(label="Datei", menu=menue1)
    menuleiste.add_cascade(label="Extras", menu=menue2)

    menue1.add_command(label="Neu")
    menue2.add_command(label="Einstellungen", command=partial(option1, opt1))

    root.bind("<FocusIn>", partial(ruecksetze_focus, root, opt1, entry))
    # Bindet das Event FocusIn an das root Fenster
    root.title("Hauptfenster")
    root.geometry('1000x800')
    root.config(menu=menuleiste)

    opt1.wm_protocol('WM_DELETE_WINDOW', partial(beende_opt1, root, opt1,
                                                 menue2))
    # Übergibt dem Icon "opt1 schliessen' die Funktion beende_op1
    
    root.mainloop()

if __name__ == '__main__':
    main()
Gruss Peter
Fritz_60
User
Beiträge: 3
Registriert: Montag 31. Januar 2022, 13:48

Hallo Peter,

danke für deine ausführliche Beschreibung. Damit muß ich mich erstmal auseinandersetzen, damit ich es verstehe. :P
Ja, dass mit dem opt1.grab_set() war gewollt, weil es soll nur ein Unterfenster geöffnet werden.
Bei mir hat das Programm soweit ohne Fehler funktioniert. Ich verwende das Programm PyCharm.
Ich hatte mir schon sowas gedacht, dass man die Mausabfragen über ein Event steuern muß, nur wußte ich nicht, wie man sowas implementiert.
Bei der Darstellung vom Code, geht die Schreibweise wahrscheinlich auch weit auseinander.
Einige schreiben es so, wie ich angefangen hatte und andere verwenden deine Schreibweise mit der main() Funktion.


Gruß Fritz
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Fritz_60: Alle schreiben das mit der Hauptfunktion. Wer das nicht tut, den kann man einfach ignorieren. Wer weiss was solche Leute sonst noch so machen. 😈
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo Fritz,
bevor Du dich verrennst, noch ein paar Anmerkungen.
Pycharm ist ein mächtiges Werkzeug und es gibt dir bestimmt viele Warnmeldungen zurück. Auch wenn dein Script in Pycharm funktioniert, solltest Du die Warnungen beachten und reagieren. Auch ein Test mit einem direkten Programmaufruf, oder im Interpreter, schadet nicht, dann siehst Du ob's funktioniert. Im Interpreter bekommst Du auch Fehlermeldungen.
Wenn Du dein kleines Projekt weiter entwickeln willst, solltest Du dich in objektorientierte Programmierung,
also Klassen, einarbeiten.
Du siehst, wie viele Argumente den Funktionen übergeben werden. Vergleich das mal mit dem Beispiel mit der Klasse. Nur mit Funktionen wird das bald einmal unübersichtlich.
Nun zu deinen Vorgaben: Du willst den Focus immer wieder im Einstellungsfenster haben. Das kannst Du, so wie hier gezeigt, mit der Option transient handhaben, oder Du versteckst das root- (und eventuell andere) Fenster mit iconify, oder withdraw, dann darfst Du aber "transient(root)" nicht verwenden.
Ich finde das iconifizieren besser, denn der Anwender weiss in der Regel was er tut und soll die Kontrolle behalten. Mit grab konnte ich mich bisher nicht anfreunden, es klammert mir zu fest und ich sehe den Sinn noch nicht. Probier mal grab_set_global :o .
Weiter willst Du, dass es nur eine Instanz des Einstellungsfensters gibt. Das erreichst Du, indem Du die Untermenütaste Einstellungen nach dem Erstellen des Fensters deaktivierst. Mit dem Löschen des Einstellungsfensters aktivierst Du diese wieder.

Code: Alles auswählen

class Main():
    def __init__(self):
        self.root = tk.Tk()    
        menuleiste = tk.Menu(self.root)
        self.menue_datei = tk.Menu(menuleiste, tearoff=0)
        self.menue_einstellungen = tk.Menu(menuleiste, tearoff=0)
        menuleiste.add_cascade(label="Datei", menu=self.menue_datei)
        menuleiste.add_cascade(label="Extras",
                               menu=self.menue_einstellungen)
        self.menue_datei.add_command(label="Neu", command=None)
        self.menue_einstellungen.add_command(label="Einstellungen",
                                    command=self.erstelle_einstellungs_fenster)        
        self.root.title("Hauptfenster")
        self.root.geometry('1000x800')
        self.root.config(menu=menuleiste)    
        self.root.mainloop()

    def erstelle_einstellungs_fenster(self):
        self.optionen_fenster = tk.Toplevel()
        self.optionen_fenster.title("Einstellungen")
        self.optionen_fenster.geometry('500x500')
        self.optionen_fenster.resizable(0, 0)
        self.optionen_fenster.attributes('-topmost', True)
        self.optionen_fenster.wm_protocol('WM_DELETE_WINDOW',
                                          self.beende_optionen_fenster)
        # Übergibt dem Icon "self.optionen_fenster schliessen' ->
        # -> die Funktion beende_optionen_fenster. 
        self.entry = tk.Entry(self.optionen_fenster)
        self.entry.pack()
        self.menue_einstellungen.entryconfigure(1, state='disabled')
        # Untermenue Einstellungen wird unwirksam. 
        self.root.bind("<FocusIn>", self.ruecksetze_focus)
        # Bindet das Event FocusIn an das root Fenster.  

    def ruecksetze_focus(self, event):
        self.optionen_fenster.transient(self.root)
        # optionen_fenster sichtbar auf root. 
        self.entry.focus_set()

    def beende_optionen_fenster(self):    
        self.root.unbind("<FocusIn>")
        # Löst die Event-Bindung FocusIn vom root Fenster.   
        self.optionen_fenster.destroy() # Zerstört das optionen_fenster. 
        self.menue_einstellungen.entryconfigure(1, state='normal')
        # Untermenue Einstellungen wird wieder aktiv. 

if __name__ == '__main__':
    Main()
Gruss Peter
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@peterpy: die __init__-Methode ist dazu da, eine Klasse zu initialisieren, bei Dir wird das Objekt Main nie fertig initialisiert, sondern läuft ewig.
In __init__ werden alle Attribute angelegt, bei Dir kommen optionen_fenster und entry erst später dazu.
Fritz_60
User
Beiträge: 3
Registriert: Montag 31. Januar 2022, 13:48

Vielen Dank an alle,

jetzt weiß ich schon mal, wie man den Programmcode vom Stil her schreiben soll.
Somit war mein Einstieg nicht ganz richtig...., aber man lernt ja immer dazu.
Antworten