AttributeError when trying to place a label - python

I am trying to make a basic clicker game in Python using Tkinter. I am getting an AttributeError when trying to place a label that was created in another function from the same class (see code snip). The code running the tkinter window is NOT in the main thread, and the main thread is acting as the console. I am running aClass.funcB from the console, AFTER aClass.funcA has already executed.
class aClass:
def funcA(self):
self.aLabel = Label(top)
global window
window = self
def funcB():
global window
self = window
self.aLabel.place(<argsHere>)
The error I am getting is:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<filePathHere>", line 334, in funcB
self.aLabel.place(<argsHere>)
AttributeError: 'NoneType' object has no attribute 'aLabel'
If anyone wants to look at the actual code (that I dont understand parts of myself) please tell me.
EDIT: Here is a link to the github with the actual files https://github.com/LeotomasMC/Click-Clicker
EDIT 2: On a side note, if anyone could tell me how the window actually starts in this instance, that would be great

Try and remove all the self as I think that it keeps it to only that def, because of that funcB doesn't know that aLabel exists.

Methods in Python classes need self as first argument:
def funcB(self):
global window
self = window
self.aLabel.place(<argsHere>)
You need this argument to access aLabel.

Related

PyQt5: restricting dock widget to only move inside frame

Im creating an application with PyQt5. I wanted to create a widget that lives inside a frame and that the user can move and resize at will. Following this answer i came to the conclusion that i could use QDockWidgets to do this.
Thing is: i want my QDockWidget to only exist and be movable inside a frame of my mainwindow. It seems that setAllowedAreas() could do this restriction for me but it can only be used with QtDockWidgetAreas, and preexisting areas are just default areas of the main window.
So currently my code is like this:
#!/usr/local/bin/pydm
from PyQt5.QtWidgets import QDockWidget
from pydm import Display
class Window(Display): ##Main window class.
def __init__(self, args, macros):
super(Window, self).__init__(None, args, macros,
ui_filename="main.ui")
self.pushButton.clicked.connect(self.createDock)
def createDock(self):
self.dock=QDockWidget()
self.dock.raise_()
self.dock.show()
(pydm is amodule that inherits all classes from PyQt and allows all default functionalities to work just fine.)
The main.ui file created with QtDesigner is just a widget window with a frame and a pushbutton. The generated window is in the image below:
So obviously my DockWidget can go outside the frame. I tried self.dock.setAllowedAreas(self.myFrame) but i got the expected error when clicking the button:
Traceback (most recent call last):
File "./main.py", line 16, in createDock
self.dock.setAllowedAreas(self.frame)
TypeError: setAllowedAreas(self, Union[Qt.DockWidgetAreas, Qt.DockWidgetArea]): argument 1 has unexpected type 'QFrame'
i also tried:
from PyQt5.QtCore import Qt
and in the createDock(self) function:
myArea = Qt.DockWidgetArea(self.frame)
self.dock.setAllowedAreas(myArea)
But i got:
Traceback (most recent call last):
File "./main.py", line 17, in createDock
myArea = Qt.DockWidgetArea(self.frame)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'QFrame'
Looking at the QDockWidgetArea it doesnt look that i can make a custom Area at all. At least not that i could find.
Is there any way at all to achieve the functionality that i want?

AttributeError: StringVar instance has no attribute 'endswith' while trying to call from a Tkinter button

I would like to create a GUI that receives two paths (a directory full of .txt documents and the destination of a new .csv file created from the files of the previously mentioned folder).
I am having trouble calling the function munge():
action = tk.Button(win, text="To .csv",command=munge(input_directory,output_directory))
Nevertheless, this exception raised:
/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/bin/python2.7 /Users/user/PycharmProjects/script.py
Traceback (most recent call last):
File "/Users/user/PycharmProjects/script.py", line 82, in <module>
action = tk.Button(win, text="To .csv", command=munge(input_directory,output_directory))
File "/Users/user/PycharmProjects/script.py", line 39, in munge
test = tuple(retrive(directory))
File "/Users/user/PycharmProjects/script.py", line 31, in retrive
for filename in sorted(glob.glob(os.path.join(directory_path, '*.txt'))):
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.py", line 70, in join
elif path == '' or path.endswith('/'):
AttributeError: StringVar instance has no attribute 'endswith'
Process finished with exit code 1
How can I correctly call the aforementioned function with the help of the Button widget?. I tried to set the name of the variable as indicated here question, but it did not worked.
update
Then from an answer of this question, I tried the following:
action = tk.Button(win, text="To .csv", command=lambda:munge(input_directory,output_directory))
Based on the error message, you seem to be trying to call the endswith method of a StringVar object. If you look at the documentation for such an object you'll see there's no such method. That is why you get the error that you do.
Assuming that path is an instance of a StringVar, you must call the get method in order to have the string stored by the object:
path_string = path.get()
if path_string == "" or path_string.endswith('/'):
...
You might want to consider reading a bit on callback functions in Tkinter, here is a useful link in order to do so http://effbot.org/zone/tkinter-callbacks.htm:
For simple cases like this, you can use a lambda expression as a link
between Tkinter and the callback function:
def callback(number):
print "button", number
Button(text="one", command=lambda: callback(1))
your function is being executed as soon as your Button widget loads, you want to avoid this.
After all, it worked with: action = tk.Button(win, text="To .csv", command=lambda:munge(input_directory.get(),output_directory.get())). However, from Bryan Oakley answer I belive that this is not the correct way to do this.
You want to call the .get() method on the StringVar to get the string it contains otherwise it's just the StringVar instance.

Intercepting the close window button (Tkinter window) throws an Tcl error

I have a program that at some point opens a new window (filled with buttons and gizmo's for the user to select and play around with) that is defined as follows:
def window(self,master):
def close(self):
# change some variables
self.destroy()
top = self.top = Toplevel()
# Several lines of buttons
top.lift()
top.protocol("WM_DELETE_WINDOW",close(self))
I initially had a close button there that would wrap everything up nicely but I noticed that if the user used the standard 'X' in the corner of the window, this function obviously would not be called and that would give a lot of problems later on. I found out about the 'WM_DELETE_WINDOW' suggestion from some other questions on this website but it gives me a rather strange error:
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1630, in wm_protocol
'wm', 'protocol', self._w, name, command)
TclError: bad window path name ".33862072"
I assume that it somehow has gotten the wrong window ID and is unable to catch the event. My question is thus, is that true or not and secondly how should I continue to deal with this issue.
Let's examine this line of code:
top.protocol("WM_DELETE_WINDOW",close(self))
This line of code is saying "immediately call the function close(self), and assign the result to the protocol handler. See the problem? It's immediately calling close, likely before self has been fully constructed. You don't want the function to be called, you want to pass in a reference to the function.
Make close be a method of self (rather than an embedded function) and change the call to top.protocol to look like this (note the lack of trailing parenthesis):
top.protocol("WM_DELETE_WINDOW", self.close)
If you prefer to keep the nested function, you can use lambda:
top.protocol("WM_DELETE_WINDOW", lambda window=self: close(window))

purpose of wx.App when just showing a frame is enough

What is the use of the wx.App class (apart from what the documentation says), when you can just create a frame and .Show(True) it?
When should a wx.App class be used, or why shouldn't you create a frame and just show it?
You have to create a wx.App. If you try to instantiate wxPython classes before creating the app, it will fail:
>>> import wx
>>> frame = wx.Frame(None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python26\lib\site-packages\wx-2.8-msw-unicode\wx\_windows.py", line 505, in __init__
_windows_.Frame_swiginit(self,_windows_.new_Frame(*args, **kwargs))
wx._core.PyNoAppError: The wx.App object must be created first!
There will always be one, and only one, wx.App. Instantiating it initializes wxPython - creates the window thread, etc.
The wx.App does a bunch of behind the scenes stuff to make your application work. It does the main loop, which is what waits for the user to do something and then responds. You have to have it. As FogleBird mentioned. By the way, you should NOT have more than one. Doing so will cause weird issues, if it works at all.
Sub-classing wx.App is a valid reason when you have to do some extra functionality in your app. This applies for initiating a connection to a database or connecting over a network, loading some external data/files before firing up the main.
class MyApp(wx.App):
def OnInit(self):
# Do some work here
return True
def __init__(self):
main_frame = MyFrame(*args, **kwargs)
main_frame.Show()
def OnExit(self):
# Clean up and close the resources from the OnInit() method

How can I properly read out spin buttons in pyGTK?

For a small timer app I want to write a GTK interface where I can set the desired time. Here is a picture of the interface:
However, I am having trouble reading out the fields of the spin buttons. My envisaged procedure for this is the following:
Read out the buttons using methods for each button
Here is one of the methods that does this:
# Get the fields of the spinbuttons
def get_seconds(self, widget, spin):
self.rSeconds = spin.get_value_as_int()
It is then called like this:
button = gtk.Button("Start")
button.connect("clicked", self.get_seconds, spinnerS)
Create a timer object with the data from the buttons
This is planned to be accomplished using this method:
# Create the timer object ...
def prepare_timer(self, widget, hours, minutes, seconds, title, text):
self.timer = eggTimer(hours, minutes, seconds, title, text)
Which is called here:
button.connect("clicked", self.prepare_timer, self.rHours, self.rMinutes, self.rSeconds, "some title", "some text")
Unfortunately, when running the script I get the following error message:
Traceback (most recent call last):
File "GTKInterface.py", line 140, in <module>
SpinButtonExample()
File "GTKInterface.py", line 126, in __init__
button.connect("clicked", self.prepare_timer, self.rHours, self.rMinutes, self.rSeconds, "Title", "Text")
AttributeError: SpinButtonExample instance has no attribute 'rSeconds'
To check whether there really is no instance of that variable, I programmed a short method to print it:
def returnS(self, widget):
print self.rSeconds
And surprisingly this method can "see" self.rSeconds. This makes me wonder what determines the visibility of the variable. What am I doing wrong to read this out?
You try to pass the attribute self.rHours to the connect method, but at that point the attribute doesn't exist yet (the clicked handlers haven't executed yet).
Note that even if you fill in self.rHours before calling connect, it will pass the value at the time of connecting, not at the time of the handler executing.
You can solve this by passing self.rHours etc directly to eggTimer in prepare_timer.
But it would be even easier to just combine all the click handlers into one, and use local variables instead of self.rHours etc. There's no reason to split your code over many click handlers like this.
Edit: BTW, you can also use nested functions instead of methods:
...
def prepare_timer(widget):
self.timer = eggTimer(
spinnerH.get_value_as_int(),
spinnerM.get_value_as_int(),
spinnerS.get_value_as_int(),
"Title", "Text")
button.connect("clicked", prepare_timer)
...
Keep it simple!
Going off of adw's answer recommending one click handler, a simple addition to your pastebin code would be:
def read_and_prepare(self,spinnerS,spinnerM,spinnerH,title,text):
self.get_seconds(spinnerS)
self.get_minutes(spinnerM)
self.get_hours(spinnerH)
self.prepare_timer(elf.rHours, self.rMinutes, self.rSeconds, title, text)
and only have
button.connect("clicked", self.read_and_prepare,spinnerS,spinnerM,spinnerH,"Title","Text")
for the connection code.
You could also probably redesign a bit to avoid all the get_* calls and just read the values in the click handler.

Categories