how structure a python3/tkinter project - python-3.x

I'm developing a small application using tkinter and PAGE 4.7 for design UI.
I had designed my interface and generated python source code. I got two files:
gm_ui_support.py: here declare tk variables
gm_ui.py : here declare widget for UI
I'm wondering how this files are supposed to be use, one of my goals is to be able to change the UI as many times as I need recreating this files, so if I put my code inside any of this files will be overwritten each time.
So, my question is:
Where I have to put my own code? I have to extend gm_ui_support? I have to create a 3th class? I do directly at gm_ui_support?

Due the lack of answer I'm going to explain my solution:
It seems that is not possible to keep both files unmodified, so I edit gm_ui_support.py (declaration of tk variables and events callback). Each time I make a change that implies gm_ui_support.py I copy changes manually.
To minimize changes on gm_ui_support I create a new file called gm_control.py where I keep a status dict with all variables (logical and visual) and have all available actions.
Changes on gm_ui_support.py:
I create a generic function (sync_control) that fills my tk variables using a dict
At initialize time it creates my class and invoke sync_control (to get default values defined in control)
On each callback I get extract parameter from event and invoke logical action on control class (that changes state dict), after call to sync_control to show changes.
Sample:
gm_ui_support.py
def sync_control():
for k in current_gm_control.state:
gv = 'var_'+k
if gv in globals():
#print ('********** found '+gv)
if type(current_gm_control.state[k]) is list:
full="("
for v in current_gm_state.state[k]:
if len(full)>1: full=full+','
full=full+"'"+v+"'"
full=full+")"
eval("%s.set(%s)" % (gv, full))
else:
eval("%s.set('%s')" % (gv, current_gm_state.state[k]))
else:
pass
def set_Tk_var():
global current_gm_state
current_gm_control=gm_control.GM_Control()
global var_username
var_username = StringVar()
...
sync_control()
...
def on_select_project(event):
w = event.widget
index = int(w.curselection()[0])
value = w.get(index)
current_gm_control.select_project(value)
sync_state()
...

Related

Reload UI, Rather Than Recreating

import sys
import webbrowser
import hou
from PySide2 import QtCore, QtUiTools, QtWidgets, QtGui
# Calling UI File & Some Modification
class someWidget(QtWidgets.QWidget):
def __init__(self):
super(someWidget,self).__init__()
ui_file = 'C:/Users/XY_Ab/Documents/houdini18.5/Folder_CGI/someUI.ui'
self.ui = QtUiTools.QUiLoader().load(ui_file, parentWidget=self)
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
self.setFixedSize(437, 42)
self.setWindowTitle("Requesting For Help")
window_C = someWidget()
window_C.show()
So, I have created this small script that shows the UI, I have connected this to Houdini Menu Bar. Now The Problem is if I click the menu item multiple times it will create another instance of the same UI & the previous one stays back, What I want is something called "If Window Exist Delete It, Crate New One" sort of thing.
Can someone guide me? I am fairly new to python in Houdini and Qt so a little explanation will be hugely helpful. Also, why can't I use from PySide6 import?? Why do I have to use from PySide2?? Because otherwise Houdini is throwing errors.
For the same thing what used to do in maya is
# Check To See If Window Exists
if cmds.window(winID, exists=True):
cmds.deleteUI(winID)
Trying to do the same thing inside Houdini.
I don't have Maya or Houdini, so I can't help you too much.
According to https://www.sidefx.com/docs/houdini/hom/cb/qt.html
It looks like you can access Houdini's main window. The main reason the window is duplicated or deleted is how python retains the reference to window_C. You might be able to retain the reference to just show the same widget over and over again by accessing the main Houdini window.
In the examples below we are using references a different way. You probably do not need your code that has
self.setParent(hou.qt.mainWindow(), QtCore.Qt.Window)
Create the widget once and keep showing the same widget over and over.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
try:
MAIN_WINDOW.window_C.show()
except AttributeError:
# Widget has not been created yet!
# Save the widget reference to an object that will always exist and is accessible
# parent shouldn't really matter, because we are saving the reference to an object
# that will exist the life of the application
MAIN_WINDOW.window_C = someWidget(parent=MAIN_WINDOW)
MAIN_WINDOW.window_C.show()
To delete the previous window and create a new window.
import hou
# Create the widget class
class someWidget(QtWidgets.QWidget):
def __init__(self, parent=None, flags=QtCore.Qt.Window): # Note: added parent as an option
super(someWidget,self).__init__(parent, flags)
...
MAIN_WINDOW = hou.ui.mainQtWindow()
# Hide the previous window
try:
MAIN_WINDOW.window_C.close()
MAIN_WINDOW.window_C.deleteLater() # This is needed if you parent the widget
except AttributeError:
pass
# Create the new Widget and override the previous widget's reference
# Python's garbage collection should automatically delete the previous widget.
# You do not need to have a parent!
# If you do have a parent then deleteLater above is needed!
MAIN_WINDOW.window_C = someWidget() # Note: We do not parent this widget!
MAIN_WINDOW.window_C.show()
Another resource shows you can access the previous widget from the page level variable. https://echopraxia.co.uk/blog/pyqt-in-houdinimaya-basic This is possible, but seems odd to me. The module should only be imported once, so the page level variable "my_window" should never exist. However, it sounds like the Houdini plugin system either reloads the python script or re-runs the import. If that is the case every time you show a new window from the import of the script, you are creating a new window. If the previous window is not closed and deleted properly, Houdini could have an ever growing memory issue.
try:
my_window.close()
except (NameError, Exception):
pass # Normal python would always throw a NameError, because my_window is never defined
my_window = MyWindow()
#This is optional you can resize the window if youd like.
my_window.resize(471,577)
my_window.show()
PySide6
https://www.sidefx.com/docs/houdini/hom/cb/qt.html
The bottom of the page shows how to use PyQt5. The same would apply for PySide6. Houdini just happens to come with PySide2.

PhotoImage GIF won't appear (noob friendly please)

Alright, so I want to make a gif display when a function has been called, but the gif will go invisible and not show up. I searched for possible answers but all of them mention "create a reference to (insert code here)" and I don't really get it because:
1. 99% of them use objects and classes in which I have 0 experience
2. Some say to make a reference with "self.img = PhotoImage(...)" which I'm pretty sure its connected to objects and classes.
3. Others only say to create a reference.
Sorry for being somewhat rude. I'm just really fed up, I searched for answers for 2 hours now.
I tried to assign the variable to global, place the variable in the function and tried to remake the gif and rename the file
This is what I tried to do
def red_flicker():
global root
red_btn_flicker = tk.PhotoImage(file='test.gif')
label_red = tk.Label(image=red_btn_flicker)
label_red.place(x=red_btn_place_x, y=red_btn_place_y)
the gif is invisible.
Please be noob friendly.
Any stuff about python 2.7 and using objects/classes will be ignored
Ok so first things first.
Your function is adding a new label every time it is call so you probably should generate the label in the global namespace once and then just apply the image to the label in the function. This way you can call the function all you want without adding more labels.
I would also move your PhotoImage to the global so you do not need to keep reopening the image each time you load the function.
By making this change we do not even need to use global as the widget creating and image loading happens in the global already.
Make sure to save the reference to the image so its not garbage collected.
import tkinter as tk
root = tk.Tk()
red_btn_flicker = tk.PhotoImage(file='test.gif')
label_red = tk.Label(root)
label_red.pack()
def red_flicker():
label_red.config(image=red_btn_flicker)
label_red.image = red_btn_flicker # saving reference to image.
red_flicker()
root.mainloop()
You must save a reference, as mentioned in the answer to this question: Why does Tkinter image not show up if created in a function?
Since you aren't using classes, you can use a global variable. For example:
def red_flicker():
global red_btn_flicker
red_btn_flicker = tk.PhotoImage(file='test.gif')
label_red = tk.Label(image=red_btn_flicker)
label_red.place(x=red_btn_place_x, y=red_btn_place_y)
Another simple technique is to attach the image as an attribute of the label itself. This works because python lets you create custom attributes on an object. However, you must make sure that the reference to the label itself isn't lost
def red_flicker():
global label_red
red_btn_flicker = tk.PhotoImage(file='test.gif')
label_red = tk.Label(image=red_btn_flicker)
label_red.place(x=red_btn_place_x, y=red_btn_place_y)
label_red.image = red_btn_flicker

deconstructing basic Tk inter script

I need help in understanding how Tk inter works.I'm using the first example from the documents page which creates a simple window with 2 buttons.
Introduction to GUI programming with tkinter
Code:
from tkinter import Tk, Label, Button
class MyFirstGUI:
def __init__(self, master):
self.master = master
master.title("A simple GUI")
self.label = Label(master, text="This is our first GUI!")
self.label.pack()
self.greet_button = Button(master, text="Greet", command=self.greet)
self.greet_button.pack()
self.close_button = Button(master, text="Close", command=master.quit)
self.close_button.pack()
def greet(self):
print("Greetings!")
root = Tk()
my_gui = MyFirstGUI(root)
root.mainloop()
Questions:
MyFirstGUI does not inherit from TK or Frame so how does it know of all the parameters (self.label,self.greet etc) one might find in the Tk class
We are passing a TK object to the variable root ( root = Tk() )
and passing that into MyFirstGUI class (my_gui = MyFirst GUI(root) )
.The only plausible explanation then, is that self.label and self.greet_button are "indeed" class variables to begin with and "become" labels ( and buttons ) once they are bound with functions such as Label(master,text="This is our first GUI!")
is my understanding correct ?
behram
So tkinter is just a library with classes inside of it, in this code you are importing the TK class, the Label class, and the Button class. When you use import statements at the top of your code, you are telling the computer to go fetch those files/functions and read them into the program. For example, if the TK class is say 100 lines of code, the statement
from tkinter import TK is equivalent to those 100 lines of code being in front of what you have written.
Now jumping to the creation of the UI itself, you create an instance of the TK class, and assign it to the variable root.
This root creates the outer window you will see when you run your UI, it holds the title, the min/max/close buttons in the top right corner, and determines the size of the window, along with a bunch of other features.
"root" is just a conventional name, it could be "potato" but as long as you know that is the foundation of your User Interface, that's what matters.
It won't be called too much outside of that first time you pass it into your class unless you're doing a lot of window manipulation.
From there, you are passing that root (window) to your MyFirstGUI object.
The __init__ function will run at the time of creation for any python class and the parameters for that function will be the parameters required to call the class. In this case, there are two parameters self, and master.
"self" is required as the first parameter of all functions inside a class so that the function knows the object it belongs to and so that it can access the class level variables available to it.
"master" has a default value of None so you could theoretically call MyFirstGUI() but in this case we are passing the root (window) as the master for the object MyFirstGUI(root).
At that point, because we are creating an instance of MyFirstGUI, __init__ fires and the first thing it does is sets self.master equal to the input of master so that it can be referenced anywhere in the object, expanding the scope beyond just the __init__ function.
From there, the function is arbitrarily defining variables that are scoped at the class level by using self. again so that these variables can be called from any part of this class.
self.label is just a name the original writer decided on, once again it could be self.macaroni as long as it makes sense to you that this is your label.
Then, because you have imported the Label class from tkinter, you are able to just refer to it as if it was already in your code. When you call these classes, they return the objects that are being set to your self.label so that you can refer to them later in the program.
If you check out the docs, you can see the Label and Button classes each have their own set of parameters available to them.
Common parameters include as you see "text" to show text that you'd like it to have, command to let a button know which function it should fire when clicked, "width" to determine the width of the widget on screen, and so on.
With the use of Default values, you don't always need to provide every single possible parameter to a call to create these objects.
Deciding what parameters to give comes down to practice and knowledge of the capabilities of each class in order to know which values you want to set and what are appropriate settings for them.
tldr:
Your import statements at the top allow you to use the classes at will, and your knowledge determines which parameters to send to each class. The docs are your friend!
I believe your understanding is correct. self.label is defined in the __init__ function by the programmer, and then assigned the widget object of a tkLabel by calling the class. From that point on, self.label is available anywhere inside the MyFirstGUI class to be manipulated as you see fit. For example, instead of the greet function printing out "Greetings!" you could change that print statement to self.label.set("Greetings!") so now your button click will change the label's text instead.
I hope this helped!

How do I implement a global "oracle" in python? [duplicate]

I've run into a bit of a wall importing modules in a Python script. I'll do my best to describe the error, why I run into it, and why I'm tying this particular approach to solve my problem (which I will describe in a second):
Let's suppose I have a module in which I've defined some utility functions/classes, which refer to entities defined in the namespace into which this auxiliary module will be imported (let "a" be such an entity):
module1:
def f():
print a
And then I have the main program, where "a" is defined, into which I want to import those utilities:
import module1
a=3
module1.f()
Executing the program will trigger the following error:
Traceback (most recent call last):
File "Z:\Python\main.py", line 10, in <module>
module1.f()
File "Z:\Python\module1.py", line 3, in f
print a
NameError: global name 'a' is not defined
Similar questions have been asked in the past (two days ago, d'uh) and several solutions have been suggested, however I don't really think these fit my requirements. Here's my particular context:
I'm trying to make a Python program which connects to a MySQL database server and displays/modifies data with a GUI. For cleanliness sake, I've defined the bunch of auxiliary/utility MySQL-related functions in a separate file. However they all have a common variable, which I had originally defined inside the utilities module, and which is the cursor object from MySQLdb module.
I later realised that the cursor object (which is used to communicate with the db server) should be defined in the main module, so that both the main module and anything that is imported into it can access that object.
End result would be something like this:
utilities_module.py:
def utility_1(args):
code which references a variable named "cur"
def utility_n(args):
etcetera
And my main module:
program.py:
import MySQLdb, Tkinter
db=MySQLdb.connect(#blahblah) ; cur=db.cursor() #cur is defined!
from utilities_module import *
And then, as soon as I try to call any of the utilities functions, it triggers the aforementioned "global name not defined" error.
A particular suggestion was to have a "from program import cur" statement in the utilities file, such as this:
utilities_module.py:
from program import cur
#rest of function definitions
program.py:
import Tkinter, MySQLdb
db=MySQLdb.connect(#blahblah) ; cur=db.cursor() #cur is defined!
from utilities_module import *
But that's cyclic import or something like that and, bottom line, it crashes too. So my question is:
How in hell can I make the "cur" object, defined in the main module, visible to those auxiliary functions which are imported into it?
Thanks for your time and my deepest apologies if the solution has been posted elsewhere. I just can't find the answer myself and I've got no more tricks in my book.
Globals in Python are global to a module, not across all modules. (Many people are confused by this, because in, say, C, a global is the same across all implementation files unless you explicitly make it static.)
There are different ways to solve this, depending on your actual use case.
Before even going down this path, ask yourself whether this really needs to be global. Maybe you really want a class, with f as an instance method, rather than just a free function? Then you could do something like this:
import module1
thingy1 = module1.Thingy(a=3)
thingy1.f()
If you really do want a global, but it's just there to be used by module1, set it in that module.
import module1
module1.a=3
module1.f()
On the other hand, if a is shared by a whole lot of modules, put it somewhere else, and have everyone import it:
import shared_stuff
import module1
shared_stuff.a = 3
module1.f()
… and, in module1.py:
import shared_stuff
def f():
print shared_stuff.a
Don't use a from import unless the variable is intended to be a constant. from shared_stuff import a would create a new a variable initialized to whatever shared_stuff.a referred to at the time of the import, and this new a variable would not be affected by assignments to shared_stuff.a.
Or, in the rare case that you really do need it to be truly global everywhere, like a builtin, add it to the builtin module. The exact details differ between Python 2.x and 3.x. In 3.x, it works like this:
import builtins
import module1
builtins.a = 3
module1.f()
As a workaround, you could consider setting environment variables in the outer layer, like this.
main.py:
import os
os.environ['MYVAL'] = str(myintvariable)
mymodule.py:
import os
myval = None
if 'MYVAL' in os.environ:
myval = os.environ['MYVAL']
As an extra precaution, handle the case when MYVAL is not defined inside the module.
This post is just an observation for Python behaviour I encountered. Maybe the advices you read above don't work for you if you made the same thing I did below.
Namely, I have a module which contains global/shared variables (as suggested above):
#sharedstuff.py
globaltimes_randomnode=[]
globalist_randomnode=[]
Then I had the main module which imports the shared stuff with:
import sharedstuff as shared
and some other modules that actually populated these arrays. These are called by the main module. When exiting these other modules I can clearly see that the arrays are populated. But when reading them back in the main module, they were empty. This was rather strange for me (well, I am new to Python). However, when I change the way I import the sharedstuff.py in the main module to:
from globals import *
it worked (the arrays were populated).
Just sayin'
A function uses the globals of the module it's defined in. Instead of setting a = 3, for example, you should be setting module1.a = 3. So, if you want cur available as a global in utilities_module, set utilities_module.cur.
A better solution: don't use globals. Pass the variables you need into the functions that need it, or create a class to bundle all the data together, and pass it when initializing the instance.
The easiest solution to this particular problem would have been to add another function within the module that would have stored the cursor in a variable global to the module. Then all the other functions could use it as well.
module1:
cursor = None
def setCursor(cur):
global cursor
cursor = cur
def method(some, args):
global cursor
do_stuff(cursor, some, args)
main program:
import module1
cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()
Since globals are module specific, you can add the following function to all imported modules, and then use it to:
Add singular variables (in dictionary format) as globals for those
Transfer your main module globals to it
.
addglobals = lambda x: globals().update(x)
Then all you need to pass on current globals is:
import module
module.addglobals(globals())
Since I haven't seen it in the answers above, I thought I would add my simple workaround, which is just to add a global_dict argument to the function requiring the calling module's globals, and then pass the dict into the function when calling; e.g:
# external_module
def imported_function(global_dict=None):
print(global_dict["a"])
# calling_module
a = 12
from external_module import imported_function
imported_function(global_dict=globals())
>>> 12
The OOP way of doing this would be to make your module a class instead of a set of unbound methods. Then you could use __init__ or a setter method to set the variables from the caller for use in the module methods.
Update
To test the theory, I created a module and put it on pypi. It all worked perfectly.
pip install superglobals
Short answer
This works fine in Python 2 or 3:
import inspect
def superglobals():
_globals = dict(inspect.getmembers(
inspect.stack()[len(inspect.stack()) - 1][0]))["f_globals"]
return _globals
save as superglobals.py and employ in another module thusly:
from superglobals import *
superglobals()['var'] = value
Extended Answer
You can add some extra functions to make things more attractive.
def superglobals():
_globals = dict(inspect.getmembers(
inspect.stack()[len(inspect.stack()) - 1][0]))["f_globals"]
return _globals
def getglobal(key, default=None):
"""
getglobal(key[, default]) -> value
Return the value for key if key is in the global dictionary, else default.
"""
_globals = dict(inspect.getmembers(
inspect.stack()[len(inspect.stack()) - 1][0]))["f_globals"]
return _globals.get(key, default)
def setglobal(key, value):
_globals = superglobals()
_globals[key] = value
def defaultglobal(key, value):
"""
defaultglobal(key, value)
Set the value of global variable `key` if it is not otherwise st
"""
_globals = superglobals()
if key not in _globals:
_globals[key] = value
Then use thusly:
from superglobals import *
setglobal('test', 123)
defaultglobal('test', 456)
assert(getglobal('test') == 123)
Justification
The "python purity league" answers that litter this question are perfectly correct, but in some environments (such as IDAPython) which is basically single threaded with a large globally instantiated API, it just doesn't matter as much.
It's still bad form and a bad practice to encourage, but sometimes it's just easier. Especially when the code you are writing isn't going to have a very long life.

Automatically creating separate instances of one class (Python - Excel/CSV)

My goal is, given an Excel or CSV file, to automatically create instances of one object. After some research, I saw that similar questions have been asked. But in most of the cases, the author only wanted to put instances into a list to print information on them (like: Python creating class instances in a loop).
What I need, is not only to create separate instances of a class, but also to be able to call on these distinct instances later in the code. Also, the main point of this, is that my file is dynamic. The one I put just below is just a toy example, my goal being to be able to automatically process bigger and more complex "models".
Let's have an example. Given the following file:
I would like to create different instances of the following object to store the information given in the file:
class Element
name = ""
Property1 = []
Property2 = []
def add_name(self, name):
self.name = name
def add_pos_reg(self, p1):
self.Property1.append(p1)
def add_neg_reg(self, p2):
self.Property2.append(p2)
I thought of using the classic way of instancing an object in a loop, and then stocking the instances in a list:
ListeElement = []
for i in range(2, max_row):
e=Element("get the property from the file") ## I already have a custom function to get the properties from the file into the instance. ##
ListeElement.append(e)
But then, I think that this way does not create distinct instances, and also I am not even sure that I will be able to call on specific instances stocked in the list later in my code.
I am sorry if this is a redundant question, I usually find what I want to do using the search function on this website, but I am getting stuck there.

Resources