Python - Tkinter'da Çoklu Pencere Kullanımı Hakkında

Merhaba,

Tkinter da programı kullanırken butonlara basılıp açılan yeni pencereler de işlem yaparken altta kalan diğer pencerelere geçişin ya da kullanımın olmamasını istiyorum. Bunu nasıl yapabiliriz? Ne kullanmamız gerekir?

Protocol methodu ile “X” butonuna bastığımızda işlemler yapabiliyorduk. Bu mantıkla ana pencerede bir butona yeni pencere açma komutu verilir. Yeni pencere açılır. Açıldığı için ana pencerenin durumu disabled edilir. Açılan yan pencerenin “X” butonuna bastığımızda protocol methodu ile ana pencerenin durumu enabled yapılır. Böylece yan pencere kapatılana kadar ana pencere disabled olmuş olur.

Bu methodun ilk parametresine string olarak “WM_DELETE_WINDOW” yazıyorduk sanırım öyle hatırlıyorum. İkinci parametre ise belirlediğiniz bir fonksiyon/method

Disable etmek gerekir. Bunun için, pencere niteliklerini değiştirmek gerekir aynı zamanda kapanırken açılırken takibi gerekir. Bunun üzerinde ısrarlı iseniz, çalışan bir kod araştırıp eklerim.

Daha basit bir yöntem. Pencerelerin görünürlüğünü değiştirmek gibi duruyor.

# Import Library
from tkinter import *
 
root = Tk()
root.title("Main Window")
root.geometry("200x200")
 
second = Toplevel()
second.title("Child Window")
second.geometry("400x400")
second.withdraw()

 

def show_main():
    root.deiconify()
    second.withdraw()
# Hide the window
def show_second():
    root.withdraw()
    second.deiconify()

Button(root, text="Show Second Window", command=show_second).pack(pady=10)
Button(second, text="Show Main window", command=show_main).pack(pady=10)


root.mainloop()

Bununla iki pencere arasında butonla geçiş yaptığınızda diğerini görünmez hale getirir.

Ama yine sistem butonlarını denetim altına almazsanız, sorun yaşabilirsiniz. Sağ köşedeki çarpı işaretine basarsanız. Çarşı karışır.

Bunu da sizin ayrıca kontrol etmeniz gerekir ki forumda bununla ilgili örnek yapmıştım, bulabilirseniz, yada bulabilirsem buraya eklerim.

İhtiyacınıza göre kodu geliştirmek gerekecek.

1 Beğeni

Mantıklı,

Sanırım şu kodu kaynak alıp bu fourmda bir örnek yapmıştık.

python - How do I handle the window close event in Tkinter? - Stack Overflow

Bu durumda çarpı ile kapattığında kontrol edebiliriz. Yani benim yukarıda yazdığım kodla beraber, kapatıldığında yapılacakları da koda ekleyebiliriz. Denemek ister misiniz?

EDIT:

Tamam ben yapayım. :slight_smile:

from tkinter import *
 
root = Tk()
root.title("Main Window")
root.geometry("200x200")

second = Toplevel()
second.title("Child Window")
second.geometry("400x400")
second.withdraw()

   

def on_closing():
    root.deiconify()
    second.withdraw()

def show_main():
    root.deiconify()
    second.withdraw()


def show_second():
    root.withdraw()
    second.deiconify()

Button(root, text="Show Second Window", command=show_second).pack(pady=10)
Button(second, text="Show Main window", command=show_main).pack(pady=10)

second.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

EDIT 2:

on_closing() ve show_() main fonksiyonları aynı, özellikle ayrı gösterdim şimdilik, hangisini hangisi kullanıyor görülebilsin diye. İstenirse tek fonksiyon çağırılarak kullanılabilir.

4 Beğeni

@semtex üstadım bunlar güzel hoş gerçekten beğendim lakin bunlara ek olarak ufak bir fantezi dile getirmek isterim. Farklı bir pencere açtığım da diğer pencerelerin kaybolmasını istemiyorum. Bununla birlikte çağıracağım fonksiyonlar aynı dosya içerisinde yer almayacağı için farklı dosyalardan modül olarak çağıracağım için de buna uygun algoritmasını kurmam lazım.

Fantazi değil, tabi ki olması gereken bir durum istediğiniz.

Tam da bunun için söyledim bu sözü. Normalde, visual basic, c# gibi dillerde, basit bir disable window properties özelliklerini true false yaparak bu iş çözülür.

Ama iş tkinter olduğunda, çapraz platform çalışması için bazı özellikler budanmış oluyor.

Yani isteğiniz makul ve/fakat bunu tkinter da yapmak için bir şeylerin etrafından dolaşmamız gerekir.

# Adapted for Python 3.6 + 64-bit Windows

from ctypes import *
from ctypes.wintypes import *

WNDPROCTYPE = WINFUNCTYPE(c_int, HWND, c_uint, WPARAM, LPARAM)

WS_EX_APPWINDOW = 0x40000
WS_OVERLAPPEDWINDOW = 0xcf0000
WS_CAPTION = 0xc00000

SW_SHOWNORMAL = 1
SW_SHOW = 5

CS_HREDRAW = 2
CS_VREDRAW = 1

CW_USEDEFAULT = 0x80000000

WM_DESTROY = 2

WHITE_BRUSH = 0


class WNDCLASSEX(Structure):
    _fields_ = [("cbSize", c_uint),
                ("style", c_uint),
                ("lpfnWndProc", WNDPROCTYPE),
                ("cbClsExtra", c_int),
                ("cbWndExtra", c_int),
                ("hInstance", HANDLE),
                ("hIcon", HANDLE),
                ("hCursor", HANDLE),
                ("hBrush", HANDLE),
                ("lpszMenuName", LPCWSTR),
                ("lpszClassName", LPCWSTR),
                ("hIconSm", HANDLE)]

user32 = ctypes.WinDLL('user32',use_last_error=True)
user32.DefWindowProcW.argtypes =[ HWND, c_uint, WPARAM, LPARAM]

def PyWndProcedure(hWnd, Msg, wParam, lParam):
    if Msg == WM_DESTROY:
        user32.PostQuitMessage(0)
        return 0
    return user32.DefWindowProcW(hWnd, Msg, wParam, lParam)
  


def main():
    WndProc = WNDPROCTYPE(PyWndProcedure)
    hInst = windll.kernel32.GetModuleHandleW(0)
    wclassName = u'My Python Win32 Class'
    wname = u'My test window'
    
    wndClass = WNDCLASSEX()
    wndClass.cbSize = sizeof(WNDCLASSEX)
    wndClass.style = CS_HREDRAW | CS_VREDRAW
    wndClass.lpfnWndProc = WndProc
    wndClass.cbClsExtra = 0
    wndClass.cbWndExtra = 0
    wndClass.hInstance = hInst
    wndClass.hIcon = 0
    wndClass.hCursor = 0
    wndClass.hBrush = windll.gdi32.GetStockObject(WHITE_BRUSH)
    wndClass.lpszMenuName = 0
    wndClass.lpszClassName = wclassName
    wndClass.hIconSm = 0
    
    regRes = windll.user32.RegisterClassExW(byref(wndClass))
    
    hWnd = windll.user32.CreateWindowExW(
    0,wclassName,wname,
    WS_OVERLAPPEDWINDOW | WS_CAPTION,
    CW_USEDEFAULT, CW_USEDEFAULT,
    300,300,0,0,hInst,0)
    
    if not hWnd:
        print('Failed to create window')
        exit(0)
    print('ShowWindow', windll.user32.ShowWindow(hWnd, SW_SHOW))
    print('UpdateWindow', windll.user32.UpdateWindow(hWnd))

    msg = MSG()
    lpmsg = pointer(msg)

    print('Entering message loop')
    while windll.user32.GetMessageA(lpmsg, 0, 0, 0) != 0:
        windll.user32.TranslateMessage(lpmsg)
        windll.user32.DispatchMessageA(lpmsg)

    print('done.')
    
    
if __name__ == "__main__":
    print ("Win32 Application in python")
    main()

Yukarıdaki kod 64 bit windows için bir pencere oluşturma örneği.

Burada görebileceğiniz üzere bir pencerenin niteliklerini, WNDCLASS içinde belirler, wndcreate içinde oluştururken de nitelikleri kayderiz.

Sizin istediğiniz, alttaki pencereye yeniden nitelik verip disable hale getirmek gerekir.

Tabi ki imkansız diye bir şey yoktur. Bir şekilde etrafından dolaşırız da belki daha kolay bir yolu da vardır ama iş bize tkinter kütüphanesini yeniden yazmaya kadar götürebilir.

İsterseniz onu da deneriz sorun değil ama bu kadar enerji harcamaya değer mi emin olamadım.

Benlik sorun yok, siz bu kadar uğraşmak ister misiniz asıl konu bu.

Bakalım yine de kolay bir yol bulabileceğimize inanıyorum. Bakalım diğer katılımcılardan neler gelecek.

Sizin çözümlerinize göre daha basit bir şey düşündüm.

from tkinter import *
from tkinter import messagebox

class MainWindow(Tk):
    def __init__(self):
        super().__init__()
        self.place_widgets()
        self.mainloop()

    def place_widgets(self):
        button = Button(self,text="Bana Tıkla!",command=self.open_new_window)
        button.pack()

    def open_new_window(self):
        Window(parent=self)


class Window(Toplevel):
    
    def __init__(self,parent):
        self.parent = parent
        super().__init__(self.parent)
        self.grab_set()
        self.setup()

    def setup(self):
        self.protocol("WM_DELETE_WINDOW",self.destroy_)

        
    def destroy_(self):
        self.destroy()

MainWindow()
2 Beğeni

c# a geçmeyi düşündüm lakin ona ayıracak enerjiyi bulamadım. aslında bir kaç ay c# a vakit ayırsam windows için daha düzenli uygulamar geliştirmek mümkün olacak.

import tkinter as tk
from tkinter import ttk


class Window(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)

        self.geometry('300x100')
        self.title('Toplevel Window')

        ttk.Button(self,
                text='Close',
                command=self.destroy).pack(expand=True)


class App(tk.Tk):
    def __init__(self):
        super().__init__()

        self.geometry('300x200')
        self.title('Main Window')

        # place a button on the root window
        ttk.Button(self,
                text='Open a window',
                command=self.open_window).pack(expand=True)

    def open_window(self):
        window = Window(self)
        window.grab_set()


if __name__ == "__main__":
    app = App()
    app.mainloop()

Bu şekilde işinize yarar mı?

Creating multiple Windows Using Tkinter Toplevel Class (pythontutorial.net)

1 Beğeni

e bu olmuş sanki dimi?

yarayacağını düşünüyorum şimdiden

Denediğim kadarıyla pencere açık kalıyor ve diğer pencerelerle herhangi bir etkileşim olmuyor. Sizin de dediğiniz bu galiba.

2 Beğeni

Bu çalışıyor kodu deneyip eklerim hep.

Burada ilk bunu paylaşmamın nedeni, üst üste beş tane pencere açtık diyelim, ilk dördünü durdurup, en üsteki çalışır mı emin olamadım.

Benim hayalimde üst üste birden fazla pencere açılabileceği ihtimali vardı.

İşe yarıyor, elinize sağlık, bunu ikiden fazla pencere açarak da yapabiliyor muyuz?

Bir örnek yapabilir misiniz?

Yani sıralı 3 pencere açtık, sırayla geriye doğru gelebiliyor muyuz gibi bir örnek.

EDIT:

Şundan dolayı sordum:

Python Tkinter - Toplevel Widget - GeeksforGeeks

from tkinter import *
 
 
# Create the root window
# with specified size and title
root = Tk() 
root.title("Root Window") 
root.geometry("450x300") 
 
# Create label for root window
label1 = Label(root, text = "This is the root window")
   
# define a function for 2nd toplevel
# window which is not associated with
# any parent window
def open_Toplevel2(): 
     
    # Create widget
    top2 = Toplevel()
     
    # define title for window
    top2.title("Toplevel2")
     
    # specify size
    top2.geometry("200x100")
     
    # Create label
    label = Label(top2,
                  text = "This is a Toplevel2 window")
     
    # Create exit button.
    button = Button(top2, text = "Exit",
                    command = top2.destroy)
     
    label.pack()
    button.pack()
     
    # Display until closed manually.
    top2.mainloop()
      
# define a function for 1st toplevel
# which is associated with root window.
def open_Toplevel1(): 
     
    # Create widget
    top1 = Toplevel(root)
     
    # Define title for window
    top1.title("Toplevel1")
     
    # specify size
    top1.geometry("200x200")
     
    # Create label
    label = Label(top1,
                  text = "This is a Toplevel1 window")
     
    # Create Exit button
    button1 = Button(top1, text = "Exit",
                     command = top1.destroy)
     
    # create button to open toplevel2
    button2 = Button(top1, text = "open toplevel2",
                     command = open_Toplevel2)
     
    label.pack()
    button2.pack()
    button1.pack()
     
    # Display until closed manually
    top1.mainloop()
 
# Create button to open toplevel1
button = Button(root, text = "open toplevel1",
                command = open_Toplevel1)
label1.pack()
 
# position the button
button.place(x = 155, y = 50)
   
# Display until closed manually
root.mainloop()

Üst üste üçüncüyü açınca ilk pencere etkisizleştirilmemiş oluyor. Yani doğru çalışmadı.

grab_set metodunun toplevel sınıfı örnekleri arasından kendi örneğine odaklıyor olması lazım.
Pencereler arası geçiş için şöyle bir şey düşündüm.

from tkinter import *
import random 

child_window_list = []

class MainWindow(Tk):
    def __init__(self):
        super().__init__()
        self.place_widgets()
        self.mainloop()

    def place_widgets(self):
        button = Button(self,text="Bana Tıkla!",command=self.open_new_window)
        button.pack()

    def open_new_window(self):
        Window(parent=self,group=child_window_list)
        Window(parent=self,group=child_window_list)
        random.choice(child_window_list).grab_set()


class Window(Toplevel):
    def __init__(self,parent,group=None):
        self.parent = parent
        self.group = group
        super().__init__(self.parent)
        group.append(self)
        self.setup()    
        
    def setup(self):
        self.protocol("WM_DELETE_WINDOW",self.destroy_)
        self.geometry("600x600")
        self.footer = Frame(self,bg="white",height=80)
        self.footer.pack(side="bottom",fill="x")
        self.prev_button = Label(self.footer,text="<",bg="ghostwhite",font=("System",25))
        self.prev_button.pack(side="left",padx=50,pady=40)
        self.prev_button.bind("<Button-1>",self.prev_window)
        self.next_button = Label(self.footer,text=">",bg="ghostwhite",font=("System",25))
        self.next_button.pack(side="right",padx=50,pady=40)
        self.next_button.bind("<Button-1>",self.next_window)
    
    def next_window(self, event):
        self.grab_release()
        self.group[self.group.index(self)+1].grab_set() 
    
    def prev_window(self, event):
        self.grab_release()
        self.group[self.group.index(self)-1].grab_set()                                                            
                            
    def destroy_(self):
        self.destroy()

MainWindow()

Ayrıca sizin dediğiniz gibi artarda 3 tane pencere açılınca yalnızca birini kilitlemesi gibi bir sorun yaşamadım. Verdiğiniz kodda oluşturulan toplevel örneklerinin parentinin olmamasından dolayı oluyor olabilir.

2 Beğeni

Elinize sağlık, aradığımı buldum sanırım:

from tkinter import *
from tkinter import messagebox

def disable_windows(window):
    for child in window.winfo_children(): # Get all the child widgets of the window
        if isinstance(child, Tk) or isinstance(child, Toplevel): # Check if the child is a Tk or Toplevel window so that we can disable them
            child.attributes('-disabled', True)
            disable_windows(child)

def enable_windows(window):
    for child in window.winfo_children(): # Get all the child widgets of the window
        if isinstance(child , Tk) or isinstance(child , Toplevel): # Check if the child is a Tk or Toplevel window so that we can enable them
            child.attributes('-disabled' , False)
            enable_windows(child)

def increase_popup_count():
    global popup_count
    popup_count += 1
    if popup_count > 0: # Check if a popup is currently active so that we can disable the windows
        disable_windows(root)
    else: # Enable the windows if there is no active popup
        enable_windows(root)

def decrease_popup_count():
    global popup_count
    popup_count -= 1
    if popup_count > 0: # Check if a popup is currently active so that we can disable the windows
        disable_windows(root)
    else: # Enable the windows if there is no active popup
        enable_windows(root)

def showinfo(title, message): # A custom showinfo funtion
    increase_popup_count() # Increase the 'popup_count' when the messagebox shows up
    messagebox.showinfo(title , message)
    decrease_popup_count() # Decrease the 'popup_count' after the messagebox is destroyed

def show():
    showinfo("Test Popup", "Hello world")

root = Tk()
root.title("Main Window")
root.geometry("500x500")

popup_count = 0

toplevel = Toplevel(root)
toplevel.title("Toplevel Window")
toplevel.geometry("400x400")

toplevel_2 = Toplevel(toplevel)
toplevel_2.title("Toplevel Window of Another Toplevel")
toplevel_2.geometry("300x300")

show_button = Button(root , text = "Show popup" , command = show)
show_button.place(x = 200 , y = 200)

mainloop()

python - Disable window controls when a messagebox is created in tkinter - Stack Overflow

child.attributes('-disabled', True)

Pencereleri child oluşturu, doğrudan disable geçilebiliyor.

Bunu geliştirmeye çalışabiliriz. toplevel a alternatif doğrudan istediğimiz sayıda child pencere disble olabiliyor. Sıraylada açılabilir gibi.

EDIT:

Pencere nitelikleri nasıl değiştirilir diye düşünürken. Tkinter kütüphanesi içerisinde gördüm.

 def wm_attributes(self, *args):
        """This subcommand returns or sets platform specific attributes
        The first form returns a list of the platform specific flags and
        their values. The second form returns the value for the specific
        option. The third form sets one or more of the values. The values
        are as follows:
        On Windows, -disabled gets or sets whether the window is in a
        disabled state. -toolwindow gets or sets the style of the window
        to toolwindow (as defined in the MSDN). -topmost gets or sets
        whether this is a topmost window (displays above all other
        windows).
        On Macintosh, XXXXX
        On Unix, there are currently no special attribute values.
        """
        args = ('wm', 'attributes', self._w) + args
        return self.tk.call(args)

    attributes = wm_attributes

cpython/init.py at 3.10 · python/cpython · GitHub

EDIT2:

Günahını almışım tkinter’in:

This subcommand returns or sets platform specific attributes
        The first form returns a list of the platform specific flags and
        their values. The second form returns the value for the specific
        option. The third form sets one or more of the values. The values
        are as follows:
        On Windows, -disabled gets or sets whether the window is in a
        disabled state. -toolwindow gets or sets the style of the window
        to toolwindow (as defined in the MSDN). -topmost gets or sets
        whether this is a topmost window (displays above all other
        windows).
        On Macintosh, XXXXX
        On Unix, there are currently no special attribute values.

Sırf windows özelliklerini kullanmak için fonksiyon kullanmış. Yalnız notta yazdığı üzere bunu kullanırsanız linux uyumlu olmaz.

1 Beğeni

En mantıklı ve kısa yolu bu olmuş. Diğerleri biraz fazla zorlama oldu gibi. Kodun asıl can alıcı noktası “child.attributes(”-disabled",True)" kısmı olmuş. Buradan ilerlenirse baya iyi iş çıkartılabilir. Tabi arayüz programlama için tkinter kullanmak ne kadar mantıklı bilemem.

Yok sizin uyguladığınız yöntem çok mantıklı, ama bir türlü üst üste pencere sayısını artırdığımda doğru çalıştıramadım. Belki onun da bir yöntemi vardır. Yani kısa temiz bir koddu.

Windows programlamaya diğer dillerde alışkınım, hepsinde nitelik geçmek kolay tek hamlede rahatça disable edebiliyordum. Tkinter kütüphanesinin buna nasıl baktığını düşünmemiştim.

Ondan çevresinden dolaşıp flagleri ben geçecektim neredeyse, ama kütüphaneye göz atayom dedim kodu gördüm.

Üzerinde çalışılabilir. En azından tek satır kodla ilgili pencereyi doğrudan disable ediyor.

Tkinter arayüz için uygunmudur bende tereddütlüyüm, ama yeni öğrenenler için basit kullanımı da artısı sanırım. Açıkcası ben çapraz platform kütüphaneler yerine, platforma özel kodalama taraftarıyım.

Bana kalsa ctypes kütüphanesiyle, doğrudan windows a özel kod yazardım. Yukarıda form oluşturma örneğini verdiğim üzere.

Tabi tkinter ve qt taraftarlarına da saygım sonsuz.

Tekrar elinize sağlık, öğretici oldu, bende hatırlamadığım konulara tekrar bakmış oldum.