I am currently restructuring my code with modules and facing a problem regarding instances.
So far I created 3 scripts: main_script.py, launchgui.py, steppermotors.py.
main_script should only create instances to launch the GUI and initialize stepper motor configuration etc.
As I need the instance GUI of launchgui.LaunchGui also in a method of the class StepperMotor I thought I could just create the instance in the same way I did in the main script. I also need the instance of StepperMotors() to call the method "calculate_angle()" within the LaunchGui class.
Unfortunately, the GUI doesn't start anymore when creating the same instances in every module and I also don't receive any error messages.
Also I think this is not really best practice.
Therefore I wanted to ask if there is a "right" and working way to share instances with other modules that only have been created in the main script?
I hope you can help me with this!
See code snippets of the affected modules below:
main_script.py
import launchgui
import steppermotors
from tkinter import *
def main():
root = Tk()
gui = launchgui.LaunchGui(root) #Creates instance 'gui' of the class 'LaunchGui' from the module 'launchgui' & initialises class
stepper = steppermotors.StepperMotors() #Creates instance 'stepper' of the class 'StepperMotors' from the module 'steppermotors' & initialises class
root.mainloop() #Mainloop for tkinter-GUI
...
launchgui.py
#own modules
import steppermotors
#other modules
from tkinter import *
class LaunchGui:
def __init__(self, root):
''' This function creates the gui with all buttons etc.
when initialising this class
'''
#Instances
stepper = steppermotors.StepperMotors()
...(Gui buttons etc. here)
#Move to Position
btn_moveto_position = Button(control_frame, text='Move to Position', command=lambda:stepper.calculate_angle([0,0,0,0]))
...
steppermotors.py
#own modules
import launchgui
#other modules
from tkinter import *
class StepperMotors:
''' This class contains the configuration and control algorithms for
the stepper motors (Axis1-4)
'''
def __init__(self):
''' Setting Output-Pins, Microstep-Resolution, Transmission and
Ramping parameters for the different motors when
initialising the class
'''
#Instances of other classes
root = Tk()
gui = launchgui.LaunchGui(root) #Creates instance 'gui' of the class 'LaunchGui' from the module 'launchgui'
...(some other functions here)
def calculate_angle(self, previous_angles_list):
''' This function calculates the difference (=delta) between
the current and previous slider_values (=angle)
'''
#Current angles
current_angles_list = [] #Creates empty list to append current values
delta_angles_list = [] #Creates empty list to append difference between current and previous angles (=delta)
for idx in range(self.number_of_motors): #Looping through list indices (idx)
current_angles_list.append(gui.SLIDER_list[idx].get()) #Appends currently selected slidervalues (=current angle) to current_angles_list
deltas_list.append(int(current_angles_list[idx] -
previous_angles_list[idx])) #Calculates the difference between current and previous angle as an integer and appends it to delta list
Related
for a python program I created a gui with QtDesigner. Inside my program the gui is initiated and calls the .ui-file to implement content.
The gui class object is than given to a Controller, and here it gets tricky:
Instead of one Controller, there are a main controller and some sub-controller for different parts of the gui. The main Controller does one or two general things and than hands over different parts of the gui to different sub controller.
See the following example for better understanding:
import Controller # import the folder with the controller
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window.part_1)
Controller.Sub_Controller_2(window = self.window.part_2)
Controller.Sub_Controller_3(window = self.window.part_3)
...
The sub-Controller is set up like this:
def __init__(self, window):
self.window = window
... # do stuff:
-----------------------------------------
by trying to call a widget in this Sub-Controller (lets say its a label called timmy), i get an error:
self.window.timmy.setVisible(False)
AttributeError: 'QWidget' object has no attribute 'timmy'
but by using the children()-Method, which returns a list of all children in the gui, I may access this label:
self.window.children()[1].setVisible(False)
This works well and hides timmy.
By trying to do this in the main Controller, it works fine as usual:
self.window.timmy.setVisible(False) # works fine here
I also tried to save the sub-controller object like this:
def sub_controls(self):
self.save_part_1 = Controller.Sub_Controller_1(window = self.window.part_1)
but this doesn't work.
Does any one have a suggestion, how I could solve this Problem?
Sure, I couldt access just all widgets with the children()-method-list, but this is laborious because of the great amount of widgets. Same thing applies to reassigning every child.
PS:
Unfortunately I cannot show you the original Code due to company guidelines.
Ok so I figured out, that the tree Structure of in each other nested widgets has nothing to do with the naming of the widgets. For example:
MainWindow
->centralWidget
->otherWidget
->Button
In this you can address "Button" with self.Button, because (how I believe) the name is saved in MainWindow-Level. If you cut out "otherWidget", the QPushButton in it still exists, but cannot addressed by name.
So it's not possible to just hand over a part of your Gui to a Handler, at least if it's build with QtDesigner.
My Solution for me to the original Problem is to hand over the complete Gui to every sub handler:
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window)
Controller.Sub_Controller_2(window = self.window)
# Controller.Sub_Controller_3(window = self.window.part_3) # before
Following is code for a tkinter listbox, created as a class, and saved as a module named ModListbox:
import tkinter as tk
class Lstbox(tk.Listbox):
def __init__(self,master,listname):
super().__init__(master)
# insert list
self.insert(0,*listname)
# place listbox
self.place(x=10,y=10)
Likewise, following is the simplest code for a main app that imports the module and displays the listbox.
import tkinter as tk
root=tk.Tk()
MyList = ['first','second','third','fourth','fifth']
import ModListbox
NewListbox = ModListbox.Lstbox(master=root,listname=MyList)
root.mainloop()
In order to add functionality to the listbox, the obvious approach is to load the module and create an instance of the class, then to bind the listbox to a function, as follows, within the main application code. So clicking on the listbox triggers an event.
import tkinter as tk
root=tk.Tk()
# function on user selection in listbox
def UserClickedNewListbox(event):
SelectedIndex = NewListbox.curselection()[0]
SelectedText = NewListbox.get(SelectedIndex)
print("You selected ", SelectedText)
MyList = ['first','second','third','fourth','fifth']
import ModListbox
NewListbox = ModListbox.Lstbox(master=root,listname=MyList)
# bind listbox to function
NewListbox.bind('<<ListboxSelect>>',UserClickedNewListbox)
root.mainloop()
However, one reason among many that I'm creating tkinter widgets as classes is to keep as much code in the modules as possible, and reduce the code in the main app. So here is the same module, but with the binding and function included inside the module.
import tkinter as tk
class Lstbox(tk.Listbox):
def __init__(self,master,listname):
super().__init__(master)
# insert list
self.insert(0,*listname)
# place listbox
self.place(x=10,y=10)
# bind user selection to function
self.bind('<<ListboxSelect>>',self.UserClickedListbox)
def UserClickedListbox(self,event):
SelectedIndex = self.curselection()[0]
# How do I trigger an action inside the main app from here?
So now, inside the module itself, a user click on the listbox triggers the function. The function 'knows' the index of the listbox clicked by the user. How, if it is possible at all, would I trigger an event inside the main application, while also passing the selected index? Is this possible? Any advice appreciated.
I am currently working on a project that takes a camera stream and after doing image processing, adds some overlays to measure some features of the image.
I am using tkinter for window management, threads to liberate the main thread (showing the camera stream) from image processing (sometimes quite lengthy operations and also non-live-showing critical dependency) and recently I moved to modules as the main script was +1000 lines.
Here I needed to make a decision about how I manage the project because I am using the window to show some updated values from the image processing, so I am quite literally "transporting" the window between modules (sending it as an argument in the buttons) so I can update values of the window in the aforementioned modules. Here is a MWE of what I am doing:
main.py:
import modules.win_manager as win_manager
if __name__ == "__main__":
win = win_manager.create_window()
win = setup_window(win)
win.root.mainloop()
win_manager.py in modules folder:
from functools import partial
from modules.cam import *
import tkinter as tk
def create_window():
root = tk.Tk()
return root
def img_proc(img,win):
img, val = process(img) #arbitrary image processing in a different file, imag_proc.py
win.objects.strv.set("Finished processing new value {}".format(val))
def cmd(win):
img = win.cam.get_camera()
img_proc(img,win)
def setup_window(win):
win.root = create_window()
win.objects= type('objects', (), {})()
win.objects.strv = strvar=tk.StringVar()
strvar.trace("w",lambda name, index, mode,strvar=strvar:win.cb(strvar))
root = tk.Tk()
win.objects.label=tk.Label(root,text="label 1")
win.objects.entry=tk.Entry(root,textvariable=strv)
win.button=tk.Button(root,text="action",command=partial(cmd,win))
win.cam = cam(win)
Finally the camera module cam.py in modules folder:
import threading
import cv2 as cv
class camera(threading.Thread):
def __init__(self,win,ini):
self.threads = type('threads', (object,), {})()
threading.Thread.__init__(self,daemon=True)#stops thread on exit
self.queue = queue.Queue()
self.queue.flag = False # Init to a "False" i.e. task is not completed
self.threads.flag = False
self.capture = init_camera() #arbitary function to get the camera with CV
def get_camera():
self.frame = self.capture.read()
return self.frame
My code is several more lines (i.e. more tkinter buttons which update several info fields) and the modules have many more functions, but the gist of what the functions do scale pretty much the same as in this code: Buttons take a window object using partial which has all the variables, objects and flags that I may need. In this case, my question is if it is worth all this internal transporting of my window object, and I should have instead made it a global object.
The code is working perfectly at the moment, the window gets updated with all the running threads, and so far there are no major bugs during runtime. My main question is if this
object passing through modules is something that is pythonic and somewhat good practice, or if I should opt for more global variables (especially the window object which has smaller objects that are frequently updated).
EDIT: Added martineau's correction of terminology.
We have a functioning program that uses Tkinter as its GUI. Everything works fine however different branches of the code are now using different hardware which realistically need different buttons. Hence we'd like to have the main GUI import modules representing the buttons depending on what hardware is being used.
I've cut out some of the code below, I'm interested in removing the makemenu() function to a separate module, hence when it is called in the Application __init__ (self.makemenu(master)) I would like to make that a reference to a separate module. I've tried doing this and am having trouble. Is this even possible?
I'm a little confused on the parent structure, what needs to be passed to my button module, etc.? I know this is a poorly constructed question but if anyone is able to advise if this is possible and put my on the right track that would be great. For example if someone could show how to modify this code to have the buttons defined in a separate module I could figure out how to do the same in my module.
# Import necessary libraries
import sys
import os
import Tkinter as tk
class Application(tk.Frame):
##################################################################
## Final functions are designed to initialize the GUI and
## connect various mouse movements to useful functions.
##################################################################
def definevars(self):
'''Original definition of all of the key variables that
we need to keep track of while running the GUI
'''
self.disable = True
self.savimgstatus = 'off'
self.mode = 'Standby'
self.status = 'Not Ready'
def makemenu(self,master):
''' Function to create the main menu bar across
the top of the GUI.
'''
self.menubar = tk.Menu(master)
## Motor Submenu
motormenu = tk.Menu(self.menubar,tearoff=1)
motormenu.add_command(label='ALT',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('alt'))
motormenu.add_separator()
motormenu.add_command(label='AZ',state='disabled')
motormenu.add_command(label='error check',
command=lambda: self.geterror('az'))
self.menubar.add_cascade(label='Tracker Motors',menu=motormenu)
## Set the big menu as the main menu bar.
master.config(menu=self.menubar)
def __init__(self,tcpconn,DOME,TRACKERSTAGE, master=None):
'''Main function to initialize the GUI. Will scale
the size of the GUI to fit any size screen... to a
point. It will not allow it to be smaller than
600x800.
'''
self.buf = 1024
## Check resolution of screen. Make GUI 2/3rds of size
## unless that means under 600x800.
fh = round(master.winfo_screenheight()*2./3.)
fw = round(master.winfo_screenwidth()*2./3.)
if fh < 600: fh = 600
if fw < 800: fw = 800
print 'GUI resolution set to {0} x {1}'.format(fw,fh)
self.fw = fw
self.fh = fh
self.imwidth = int(0.45*self.fw)
self.imheight = int(0.45*self.fh)
self.imcentx = self.imwidth/2
self.imcenty = self.imheight/2this
## Initialize Frame
tk.Frame.__init__(self, master, height=fh,width=fw)
self.grid()
self.grid_propagate(0)
## Initialize Various variables.
self.definevars()
## Create buttons, etc.
self.createWidgets()
self.makemenu(master)
self.disableall()
## Main Loop function
self.checkoutput()
###################################################################
# Initialize GUI window.
root = tk.Tk()
root.title('Hardware') # window title
app = Application(master=root)
app.mainloop() # go into the main program loop
sys.exit()
If you want to move makemenu to a separate module, that should be pretty simple. However, you'll need to change a few things.
Since makemenu no longer has a reference to self (or has a different reference, if you implement it as a separate class), you need to replace calls like command=lambda: self.geterror('alt')) to be command=lambda: master.geterror('alt')).
The other thing I recommend is to remove the call to add the menu to the root. I believe that modules shouldn't have side effects like this -- the function should make a menu and return it, and let the caller decide how to use it, ie:
self.menubar=makemenu(master)
master.configure(menu=self.menubar)
Roughly speaking, this is a variation of the MVC (model/view/controller) architectural pattern where the Application instance is your controller (and also part of the view unless you make modules of all your UI code). The menu is part of the view, and forwards UI functions to the controller for execution.
Your application then looks something like this:
from makemenu import makemenu
class Application(...):
def __init__(...):
...
self.menubar = makemenu(master)
master.config(menu=self.menubar)
...
Is there a way to wrap a tkinter GUI in a class that can be created and interacted with from another object? For this to be really useful, the class has to work so mainloop or its equivalent doesn't hold the application. If so, can someone point me to a working example?
For context, what I'm trying to do is make a SimpleUI class that I can use in any application to allow it to display information or register functions to be executed on button or key presses. So any threading, queues, etc. I would like to have hidden in the SimpleUI class.
Based on what I've gathered from reading around, the answer is No without re-implementing mainloop. Rather, the GUI should be the main application which farms out work through one method or another. However, this would make any application with tkinter (perhaps other GUIs as well?) feel like the tail is wagging the dog. Hopefully I have misunderstood what I have beeing reading.
I know this may seem like a repost of this and other similar questions but I can't comment on those and the answers seem to be doing the opposite of what I want to do. In addition to that question, I've found bits and pieces of related code in many places, but I have not been able to put them together. If there is a way, I'll learn Python threads or whatever to make it work.
I'm using Python 3.1 if it makes any difference.
Example of how I would like it to work.
ui = SimpleUI()
ui.hide()
ui.show()
ui.add_frame...
ui.add_button...
ui.register_function(button, function)
Is this what you're looking for?
#The threading module allows you to subclass it's thread class to create
#and run a thread, in this case we will be starting SimpleUI in a seperate thread
import threading
from Tkinter import *
def printfunction():
print "ButtonPress"
class NewClass:
def __init__(self):
self.ui = SimpleUI()
self.ui.add_frame("frame1")
self.ui.add_button("button1","frame1")
self.ui.register_function("button1",printfunction)
self.ui.start()
#self.ui = Threader().start()
def PrintSuccess(self):
print "Success!"
class SimpleUI:
def __init__(self):
self.root = Tk()
self.frames = {}
self.buttons = {}
def start(gui):
class Threader(threading.Thread):
def run(self):
gui.root.mainloop()
Threader().start()
def hide(self):
self.root.withdraw()
if(raw_input("press enter to show GUI: ")==""):self.show()
def show(self):
self.root.update()
self.root.deiconify()
def add_frame(self,name):
tmp = Frame(self.root)
tmp.pack()
self.frames[name] = tmp
def add_button(self,name,frame):
tmp = Button(self.frames[frame])
tmp.pack()
self.buttons[name] = tmp
def register_function(self,button,function):
self.buttons[button].config(command=function)
NC = NewClass()
NC.PrintSuccess()