Use transient when importing to another module - python

Well, here turning to the forum for help. I have a routine in which I import module dos.py, and I need module dos.py (toplevel) to always be in front of my main screen uno.py. I cannot get transient to work, previously I had no problems because I did not need the screen that is always present, but now I need my screen dos.py to be always ahead of one.py.
Here I have the problem, I do not apply transient when it comes to importing modules
I leave the routine very short to understand how transient is handled, thank you.
# uno.py
import tkinter as tk
class PRUEBA:
def __init__(self):
ventana_principal = tk.Tk()
ventana_principal.geometry ("600x600")
ventana_principal.config (bg="blue")
ventana_principal.title ("PANTALLA PRINCIPAL")
def importar():
from dos import toplevel
toplevel()
boton = tk.Button (ventana_principal , text = "boton" , command = importar)
boton.pack ( )
ventana_principal.mainloop()
PRUEBAS = PRUEBA ()
**module dos.py
#dos.py
import tkinter as tk
class toplevel:
def __init__(self):
secundario = tk.Toplevel ()
secundario.geometry ("150x40+190+100")
secundario.resizable(0,0)
secundario.transient(self.master) """ Here I have the problem not to apply transient
when it comes to importing modules """
sorry

Your code will raise exception:
AttributeError: 'toplevel' object has no attribute 'master'
The line secundario.transient(self.master) should be secundario.transient(secundario.master) instead.
But the instance of toplevel is useless because you cannot access secundario later as it is a local variable.
Suggest to make toplevel inherit from tk.Toplevel:
class toplevel(tk.Toplevel):
def __init__(self, parent, *args, **kw):
super().__init__(parent, *args, **kw)
self.geometry("150x40+190+100")
self.resizable(0, 0)
self.transient(parent)
And then create it like:
top = toplevel(ventana_principal)

Related

Modular Tkinter Window Opens Two Window When Run

When I run my Tkinter code the results end up giving me two Tkinter windows. One has the widgets/items my code says but one is a completely blank window.
file 1-
from tkinter import*
class Screen:
def __init__(self, items):
self.Tk = Tk()
self.items = items
self.Tk.mainloop()
file 2-
from tkinter import*
from Screen import*
class Module1:
def __init__(self):
Test_button = Button(text="Test")
Test_button.pack()
items = Module1()
Mainscreen = Screen(items)
I just want to make a modular Tkinter screen so my code is not that messy đŸ˜…
-Just want one/common Tkinter window
I think the trouble is you're going about things backwards a bit. You'll want to import your modules into the Python file containting your "root" window class (aka Screen), rather than the other way around.
The typical way to handle a "root" window class in tkinter usually looks like this, where the class inherits directly from Tk()
# this is the first file...
import tkinter as tk # avoid star imports!
from file2 import Module1 # import your widget classes here
# note that depending on your project structure you may need to do
# 'from .file2 import Module1' instead (this is called a 'relative import')
class Screen(tk.Tk): # the 'Screen' class inherits from 'tk.Tk'
def __init__(self):
super().__init__() # initialize tkinter.Tk()
# instance your module widgets by calling the class like so
# (you'll need to update Module1 as shown below here)
self.mod1 = Module1(self) # 'self' refers to 'Screen', the main window
You'll need to update the Module1 class so it can be bound to your parent Screen
# this is the second file...
import tkinter as tk # again, star imports bad...
class Module1:
def __init__(self, parent): # add the parent parameter
# whenever you instance your 'Module1' class, it will expect a 'parent';
# above, we used self (i.e. 'Screen') - this is where your 'button' is going!
self.parent = parent
test_button = tk.Button(self.parent, text="Test")
test_button.pack()
And you'll run the mainloop() method on an instance of your Screen class at the end of your main Python file like so
# this is the first file again...
if __name__ == '__main__': # if this script is being run at the top level...
app = Screen() # get an instance of your Screen class
app.mainloop() # run the GUI loop

Python - Tkinter and OOP

I am trying to transform my procedural-programming Python project to object oriented programming.
In my project I am using Tkinter.
The procedural version worked just fine, but in OOP I get the
line 2493, in grid_configure
self.tk.call(
_tkinter.TclError: can't invoke "grid" command: application has been destroyed
error right when I try to grid the first labels.
My code:
from tkinter import *
class Document:
root = Tk()
root.geometry("1000x500")
file_name = Label(text="File Name")
document_title = Label(text="Document Title")
def gridding(self):
self.file_name.grid(row=1,column=2)
self.document_title.grid(row=2,column=2)
root.mainloop()
doc1 = Document()
doc1.gridding()
The error message is not helping at all, so hopefully this post will help others as well.
Many thanks for your help beforehand.
There are a lot of things to consider about your code, and I will address all of them.
You are importing all of tkinter without any alias
Calling your root "doc1" implies that you think you will be making a "doc2", "doc3", etc.. out of that class, and that is not going to work. You can't have many root instances. At best, that should be Toplevel, but only if you intend to open a new window for every new document.
You shouldn't be calling mainloop inside the class, at all. You should be using module conventions. This way, you can import elements of this script into another script without worrying about this script running things automatically. At the very least, creating a wrapper for mainloop is silly. You can already call mainloop, what is the purpose of sticking it inside another method? (doc1.root.mainloop())
Creating a method to do very specific grid placement isn't reusable. I would argue that a mixin that makes certain features more usable, but keeps them dynamic would be a better approach. It isn't necessary to create a custom wrapper for widgets, but if you are going to put a gridding method on everything then why not make a better gridding method and have it automatically "mixed in" to everything that applies? And as long as you go that far you might as well include some other conveniences, as well.
Something like Document (ie, something where you may need many or may change often) likely shouldn't be the root. It should be IN the root. It certainly shouldn't have the root buried in it as a property.
Below gives numerous examples of the things I stated above. Those examples can and should be embellished. For instance, BaseWidget could include properties for x, rootx, y, rooty etc... The way ParentWidget is designed, you can use a range as the first argument of rowcfg and colcfg to do "mass configuring" in one call. They both return self so, they can be used inline.
import tkinter as tk
from typing import Iterable
class BaseWidget:
#property
def width(self) -> int:
self.update_idletasks()
return self.winfo_width()
#property
def height(self) -> int:
self.update_idletasks()
return self.winfo_height()
def grid_(self, r=None, c=None, rs=1, cs=1, s='nswe', **kwargs):
#this allows keyword shorthand, as well as original keywords
#while also allowing you to rearrange the above arguments
#so you know the exact order they appear and don't have to use the keyword, at all
self.grid(**{'row':r, 'column':c, 'rowspan':rs, 'columnspan':cs, 'sticky':s, **kwargs})
#return self so this can be used inline
return self
class ParentWidget:
#property
def descendants(self):
return self.winfo_children()
#inline and ranged grid_rowconfigure
def rowcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.grid_rowconfigure(i, **options)
#so this can be used inline
return self
#inline and ranged grid_columnconfigure
def colcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.grid_columnconfigure(i, **options)
#so this can be used inline
return self
class Custom_Label(tk.Label, BaseWidget):
#property
def text(self) -> str:
return self['text']
#text.setter
def text(self, value:str):
self['text'] = value
def __init__(self, master, **kwargs):
tk.Label.__init__(self, master, **kwargs)
class Document(tk.Frame, ParentWidget, BaseWidget):
def __init__(self, master, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
#the rest of this class is an example based on what little code you posted
#the results are not meant to be ideal. It's a demo of usage ... a gist
self.file_name = Custom_Label(self, text='File Name').grid_(1,2)
self.doc_title = Custom_Label(self, text='Document Title').grid_(2,2)
#possible
#r = range(len(self.descendants))
#self.colcfg(r, weight=1).rowcfg(r, weight=1)
self.colcfg(2, weight=1).rowcfg([1,2], weight=1)
class Root(tk.Tk, ParentWidget):
def __init__(self, title, width, height, x, y, **kwargs):
tk.Tk.__init__(self)
self.configure(**kwargs)
self.title(title)
self.geometry(f'{width}x{height}+{x}+{y}')
self.rowcfg(0, weight=1).colcfg(0, weight=1)
doc1 = Document(self).grid_()
if __name__ == '__main__':
Root('MyApplication', 800, 600, 200, 200, bg='#000000').mainloop()
You are almost there: you should build the class in the __init__ method, and only call mainloop at the end of the setup:
from tkinter import Tk
from tkinter import Label
class Document:
def __init__(self):
self.root = Tk()
self.root.geometry("1000x500")
self.file_name = Label(text="File Name")
self.document_title = Label(text="Document Title")
self.gridding()
def gridding(self):
self.file_name.grid(row=1, column=2)
self.document_title.grid(row=2, column=2)
def start(self):
self.root.mainloop()
doc1 = Document()
doc1.start()
This creates a window with two labels, as expected.
Cheers!

Using .get() to pull a Tkinter variable from another class

I am writing a Tkinter application that requires parts of the user display to exist in two different class, both imported from another file. I need to take a piece of user input and pass it from one class to another. In the toy example below, the user is supposed to type something into my_entry_input which later the class PullVariable is supposed to access.
Code is below. I had a few thoughts, such as somehow using globals or creating a function within the original class to get the variables and then pass them back. In all cases, I get:
AttributeError: type object 'Application' has no attribute 'my_entry'
The best solution I can think of is to create a function that responds to the binding, then pass the .get() to the other class from that function. My feeling is that tkinter doesn't like .get() in between classes.
Thanks to the community for your help.
MAIN
from import_test1 import *
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable()
if __name__ == '__main__':
root.mainloop()
IMPORTED CODE
from tkinter import *
from tkinter import ttk
class Application(Frame):
def __init__(self, parent, *args, **kwargs):
print('Application init')
Frame.__init__(self, parent, *args, **kwargs)
self.parent=parent
self.parent.grid()
def entry(self):
self.my_entry = StringVar(self.parent)
my_entry_input = Entry(self.parent, textvariable=self.my_entry,
width=16)
my_entry_input.bind('<FocusOut>', self.show_entry)
my_entry_input.grid(column=0, row=1)
self.show_label = Label(self.parent, text = '')
self.show_label.grid(column=0, row=2)
def hello_world(self):
print('hello world')
self.hw = Label(self.parent, text='Hello World!')
self.hw.grid(column=0, row=0)
def show_entry(self, event):
PullVariable(Application).find_entry()
class PullVariable:
def __init__(self, app):
self.app = app
print('Pull initiated')
def find_entry(self, event=None):
self.pulled_entry = self.app.my_entry.get()
self.app.show_label['text'] = self.pulled_entry
my_entry is not a attribute of Application class, so you can't do Application.my_entry, by it is an attribute of instance of Application class, so you can do Application().my_entry. You can probably add either instance of Application or my_entry to the __init__ method of PullVariable. I'll use the former.
# ...
class PullVariable:
def __init__(self, app): # add app here
self.pulled_entry = app.my_entry.get() # use app here
print(self.pulled_entry)
# and then
root=Tk()
ui = Application(root)
ui.hello_world()
ui.entry()
pv = PullVariable(ui) # supply PullVariable with instance of Application

Tkinter Toplevel in OOP script: how?

Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()

Tkinter update values from other class

I've got a tkinter window that have 3 features: background color,foreground color, and a text label. These features is in a text config file (properties.conf) in my home folder. I want to update window features when the config file changed. I watch the changes in config file with pyinotify and I want to update window when it changes. This is the code:
#!/usr/bin/python
import threading
from Tkinter import *
import os
import ConfigParser
import pyinotify
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
Gui().updateGUI()
print 2
wm = pyinotify.WatchManager()
wm.add_watch('/home/mnrl/window_configs', pyinotify.IN_CLOSE_WRITE, onChange)
notifier = pyinotify.Notifier(wm)
notifier.loop()
class ConfigParse():
def __init__(self):
self.confDir = os.path.join(os.getenv('HOME'), 'window_configs/')
self.confFile = os.path.join(self.confDir + "properties.conf")
self.config = ConfigParser.ConfigParser()
if os.path.isfile(self.confFile):
self.config.read(self.confFile)
else:
if not os.path.exists(self.confDir):
os.makedirs(self.confDir)
self.config.add_section('bolum1')
self.config.set('section1', 'setting1', 'green')
self.config.set('section1', 'setting2', 'red')
self.config.set('section1', 'setting3', 'sample text')
with open(self.confFile, 'wb') as self.confFile:
self.config.write(self.confFile)
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text=ConfigParse().config.get('section1', 'setting3'), fg=ConfigParse().config.get('section1', 'setting1'), bg=ConfigParse().config.get('section1', 'setting2'))
self.lbl.pack()
def updateGUI(self):
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
self.lbl["fg"] = ConfigParse().config.get('bolum1', 'ayar1')
self.lbl["bg"] = ConfigParse().config.get('bolum1', 'ayar2')
self.root.update()
WatchFile().start()
Gui().root.mainloop()
But whenever properties.conf file changes a new window more appears near the old tkinter window. So tkinter window not updates, new windows open. How can I correct it?
The problem is that in WatchFile.run() you're doing this:
def onChange(ev):
Gui().updateGUI()
This does something different than what you're expecting. It creates a new GUI instance, and then immediately calls the updateGUI() method on it. Hence the two windows.
What you need to do instead, is something along the lines of:
#!/usr/bin/env python
gui = None
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
gui.updateGUI()
[...]
WatchFile().start()
gui = Gui()
gui.root.mainloop()
Here, a variable gui is created, and has an instance of your GUI class assigned to it. Later, the updateGUI() method is called on the same instance.
This problem is more or less repeated with your usage of your ConfigParser class, for example:
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
In this case, it 'works' because your ConfigParse() class can be executed twice without side-effects (such as opening windows), but it's not very efficient. You're reading the same file multiple times.
What would be better, is to just use a function (a class with only a __init__ defined is effectively the same), run this once, and return a dict.

Categories

Resources