Combining functional and OOP in tkinter - python

I made an app using tkinter which basically take user input and upon clicking the appropriate button, it will run a function to process them and return an output.
The program works as intended, however I am confused on how appropriate it is to combine functional and OOP style of program building.
My app consists of two python file. The first file, lets call it GUI.py, looks like this:
from secondfile import *
class Window:
def __init__(self, master):
self.master = master
.................
def proceed():
self.newWindow = tk.Toplevel(self.master)
self.app = Window2(self.newWindow, self)
class Window2:
........
thisThread = threading.Thread(
target =simple_func_but_expensive,
args = (....))
def run_the_prog2():
thr.start()
def main():
root = tk.Tk()
root.geometry(....)
root.title(.....)
app = Window(root)
root.mainloop()
if __name__ == '__main__':
main()
The second file (secondfile.py) consists of:
def supporting_function_1(....):
.........
def supporting_function_2(....):
.........
def supporting_function_...(....):
.........
def supporting_function_20(....):
.........
def simple_func_but_expensive(.......):
.........
I was thinking of restructuring the second file with class, but each function in secondfile.py don't really share instance variable that much. Also, the program is able to run another instance of simple_func_but_expensive() because of root.mainloop() so I don't think OOP is necessary in the function file.
However, it seems that most programs use OOP and I see it as more computer science appropriate. Any opinion on this?

Related

Python - reference class each other (information flow)

I would like to know, what is the concept of information flow in GUI based apps, or any other app with same problem. When you have two seperate classes and their objects, how is the messeging process done between them. For example you have a GUI and AppLogic.
Scenario 1: Button is pressed -> GUI is processing event -> calls AppLogic method image_clicked()
Scenario 2: HTTPServer gets a message -> AppLogic receives image -> AppLogic calls GUI method render_image()
The problem is that you cannot reference classes each other because the first class does not know the second one (here AppLogic does not know GUI class):
class AppLogic():
gui : GUI
def image_clicked(self):
pass #not important
class GUI():
app_logic : AppLogic
def render_image(self):
pass #not important
I know this is more like go to school and study problem, but I would like to know how these problems are sovled, or some common practices. At least link with some detailed information. I am not able to name the problem right to find the answer.
Edit:
I can use this code without explicit type declaration and it works. But when I want to call functions of gui in AppLogic class definition, intellisense does not hint anything, because it does not know the type of attribute gui. And I don't think that it is good practice to use code like that.
class AppLogic():
def __init__(self) -> None:
self.gui = None
def image_clicked(self):
pass #not important
class GUI():
def __init__(self) -> None:
self.app_logic = None
def render_image(self):
pass #not important
app = AppLogic()
gui = GUI()
app.gui = gui
gui.app_logic = app
You need to initialize your variables.
gui = Gui()
then you can call the methods
For example:
class AppLogic:
gui: Gui
def image_clicked(self):
gui = Gui()
gui.render_image()
class Gui:
logic: AppLogic
def render_image(self) :
pass
Or you can initialize your variable directly
gui: Gui = Gui()
I hope this answers your question
from LogicClass import Logic
class Gui:
logic: AppLogic = AppLogic()
def render_image(self) :
pass
and:
from GuiClass import Gui
class AppLogic:
gui: Gui
def image_clicked(self):
gui = Gui()
gui.render_image()
from Gui import Gui
class Logic:
def __init__(self):
self.gui = Gui()
if __name__ == "__main__":
Logic()
and
class Gui:
def __init__(self):
print("GUI")

How can I 'merge' my main program with a GUI?

Just to make a clear distinction between my questions and a lot of other questions on here:
I have already written the 'main' program (which has classes, functions, and variables alike) and a good chunk of the GUI.
So this isn't a question on how to write in tkinter or python, but more so how can I combine them?
Should I run the program from the GUI? And import the various variables, functions, and classes? And if so, should I import the whole main program of use from to import each item when needed?
Should I create a third program that imports the main and the GUI?
I just can't seem to find any clear answer, or at least I can't seem to find out how to even phrase the question because all search results point to how to write GUI, which I already get the gist of.
Here is an example of structure I did for one of my projects containing a Server (your actual main code), a GUI, and a third program I called "App" that just runs the 2. I created functions like link_with_gui or link_with_server so you can access to your GUI's variables from the Server and vice-versa.
To run this program, you just have to call python app.py. I added sections if __name__ == '__main__' in the Server and GUI so you can run them independantly (for testing purposes).
EDIT : I updated my code with threads. In the Server, you have an infinite loop that increments the variable self.count every second, and in the GUI if you click on the button, it will print this count.
App :
# app.py
from server import Server
from gui import GUI
class App:
def __init__(self):
self.gui = GUI()
self.server = Server()
self.link_parts()
def link_parts(self):
self.server.link_with_gui(self.gui)
self.gui.link_with_server(self.server)
def main():
app = App()
app.gui.mainloop()
if __name__ == '__main__':
main()
Server :
# server.py
import threading
import time
class Server:
def __init__(self):
self.count = 0
thread = threading.Thread(target=self.counter)
thread.daemon = True
thread.start()
def link_with_gui(self, gui):
self.gui = gui
def display(self):
self.gui.chat_text.delete("1.0", "end")
self.gui.chat_text.insert("insert", "This is Server count : {}".format(self.count))
def counter(self):
while True:
self.count += 1
time.sleep(1)
print("self.count", self.count)
if __name__ == '__main__':
server = Server()
time.sleep(4)
GUI :
# gui.py
import tkinter as tk
class GUI(tk.Tk): # Graphic User Interface
def __init__(self):
super().__init__()
self.btn = tk.Button(master=self, text="Click Me")
self.btn.pack()
self.chat_text = tk.Text(self, width=20, height=3)
self.chat_text.pack()
def link_with_server(self, server):
self.server = server
self.btn.configure(command=self.server.display)
if __name__ == '__main__':
gui = GUI()
gui.mainloop()

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