I usually write a small function to create QActions. But PyQt5 doesn't support function SIGNAL() anymore. I don't know how to rewrite this function beautifully.
def createAction(self, text, slot=None, signal='triggered()'):
action = QAction(text, self)
if slot is not None:
self.connect(action, SIGNAL(signal), slot)
return action
I think you may be over-thinking this. New-style signals are instance attributes, so you can just use getattr:
def createAction(self, text, slot=None, signal='triggered'):
action = QAction(text, self)
if slot is not None:
getattr(action, signal).connect(slot)
return action
The problem here is about use a string (by name) to reference a signal.
There are three ways to connect signals under PyQt5. see connecting signals by kwargs So I come out with this solution.
def createAction(self, text, slot=None, signal='triggered'):
action = QAction(text, self)
signal_dict = {'triggered':action.triggered, 'changed':action.changed,
'toggled':action.toggled, 'hovered':action.hovered }
if slot is not None:
# self.connect(action, SIGNAL(signal), slot)
signal_dict[signal].connect(slot)
return action
Related
Here is part of the code:
...
self.startButton.clicked.connect(self.conc_thread)
def conc(self):
self.textField.clear()
word=self.searchBox.text()
path=r'D:\\python1\wxPython\NYConc\Fiction'
for filename in glob.glob(os.path.join(path, '*.txt')):
try:
file=open(filename, 'r')
read=file.read()
file.close()
pattern=re.findall(r'.{40} '+word+r' .{40}', read)
for i in pattern:
self.textField.append(i)
except:
continue
def conc_thread(self):
tg=threading.Thread(target=self.conc)
tg.start()
It gives me this error message:
"QObject: Cannot create children for a parent that is in a different thread.
(Parent is QTextDocument(0xd08b98), parent's thread is QThread(0xc6f620), current thread is QThread(0x4a2c810)"
How can I solve that, please?
Given that your self.textField is a QTextEdit, whenever you call its append method, a QTextCursor is created as a child of the QTextEdit underlying QTextDocument object.
What's wrong with that? Qt will not allow any QObject-derived class to have children 'living' in a thread different from the parent's one.
How to solve that? Since you're already using them, let's employ signals and slot.
First define a signal like this:
appendText = pyqtSignal(str)
and the corresponding slot:
def appendToTextField(self, text):
self.textField.append(text)
connect them:
self.appendText.connect(self.appendToTextField)
then instead of:
for i in pattern:
self.textField.append(i)
do:
for i in pattern:
self.appendText.emit(i)
The appendToTextField slot is supposed to run in the right thread, the one where the QTextEdit underlying QTextDocument live, thus Qt will let new children be added to it.
I have a small problem with my code.
There are two classes. First one creates a window with a Options button. Upon clicking the button, the second class is called and creates another window with an Ok button. Let's say there is also a checkbox, which changes the background color to black or something like that. After clicking the button, whatever changes were made in the options are stored into a file and the second window is closed.
All of this works fine. My problem is that now I need to call method update_init from the first class that will apply those changes to the MainWindow. The code below shows my first solution to this problem, but from what I understand, by using second mainloop I create second thread, which should be avoided.
class MainWindow:
def __init__(self, master):
self.master = master
self.options_btn = tk.Button(self.master, text="Options", command=self.open_options)
self.options_btn.pack()
self.options_window = None
def open_options(self):
options_master = tk.Toplevel()
self.options_window = OptionsWindow(options_master)
options_master.mainloop()
lst = meta_load() # loads changes from a file
self.update_init(lst)
def update_init(self, lst):
#code
class OptionsWindow:
def __init__(self, master):
self.master = master
self.ok_btn = tk.Button(self.master, text="OK", command=self.update_meta)
self.ok_btn.pack()
def update_meta(self):
meta_save(12) # saves changes into a file
self.master.destroy()
main_master = tk.Tk()
main_master.minsize(width=1280, height=720)
b = MainWindow(main_master)
main_master.mainloop()
My second solution was to just put both classes into one, but the code is quite messy if I do so.
Can I somehow call the method update_init (which is in the MainWindow class) from the OptionsWindow class without initializing new MainWindow class window? Or is there any other way to deal with this? I would appreciate any help.
I am sorry if this is too specific, I've tried to make it as general as possible, but it's a very specific problem and I couldn't find much information about it anywhere on the internet.
In general you can call a class method from anywhere you want and pass anything to it without initialisation of that class's instance, thanks to objective nature of python, but beware of self dependencies! Although, I don't think that's a good practice.
class A:
def __init__(self):
self.foo = 'foo'
def return_foo(self):
return self.foo
class B:
def __init__(self):
self.bar = 'bar'
print('Ha-ha Im inited!')
def return_bar(self):
try:
return self.bar
except AttributeError:
return 'bar'
def test():
a = A()
# b = B()
return_bar = getattr(B, 'return_bar', None)
if callable(return_bar):
print('%s%s' % (a.return_foo(), return_bar(None)))
test()
Links:
getattr
callable
I have assigned one method to combobox like this
def run(self):
GetAllLayers(self) #custom Methods
#attach index changed event / passing parametric method
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(lambda arg=self: LayersValueChange(arg))
getting error here
def LayersValueChange(self):
layers = self.iface.legendInterface().layers()//here error
And Error is:
layers = self.iface.legendInterface().layers()
AttributeError:
'int' object has no attribute 'iface'
self is object but it getting it like int.
Assuming LayersValueChange is an external function and not a method of the same class, you can connect the signal like this:
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
lambda: LayersValueChange(self))
This simply ignores the parameters sent by the signal, and creates a function enclosure that allows self to be referenced later (so there's no need to use arg=self).
If you also want the index sent by the signal, you will have to change the signature of the function, and then connect the signal like this:
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
lambda index: LayersValueChange(self, index))
def LayersValueChange(self, index):
layers = self.iface.legendInterface().layers()
print(index)
However, a much better design would be to make all the functions methods of the same class. Then your code would look like this:
class MyClass(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyClass, self).__init__(parent)
...
self.dlg.cbLayerNamesAll.currentIndexChanged.connect(
self.layersValueChange)
def run(self):
self.getAllLayers()
def layersValueChange(self, index):
layers = self.iface.legendInterface().layers()
def getAllLayers(self):
...
The whole signature of your signal is: currentIndexChanged(int index) (see the doc). So the argument, your lambda is receiving is of type int and from that information Python assumes the self parameter of your LayersValueChanged is of type int.
You need to do two things:
Don’t pass self as default parameter. In fact you don’t need any default parameter, because currentIndexChanged always provides one.
Change your slot signature to correctly accept the int parameter:
def LayersValueChange(self, index):
…
I wrote a PyQt5 GUI (Python 3.5 on Win7). While it is running, its GUI is not responsive. To still be able to use the GUI, I tried to use QThread from this answer: https://stackoverflow.com/a/6789205/5877928
The module is now designed like this:
class MeasureThread(QThread):
def __init(self):
super().__init__()
def get_data(self):
data = auto.data
def run(self):
time.sleep(600)
# measure some things
with open(outputfile) as f:
write(things)
class Automation(QMainWindow):
[...]
def measure(self):
thread = MeasureThread()
thread.finished.connect(app.exit)
for line in open(inputfile):
thread.get_data()
thread.start()
measure() gets called once per measurement but starts the thread once per line in inputfile. The module now exits almost immediately after starting it (I guess it runs all thread at once and does not sleep) but I only want it to do all the measurements in another single thread so the GUI can still be accessed.
I also tried to apply this to my module, but could not connect the methods used there to my methods: http://www.xyzlang.com/python/PyQT5/pyqt_multithreading.html
The way I used to use the module:
class Automation(QMainWindow):
[...]
def measure(self):
param1, param2 = (1,2)
for line in open(inputfile):
self.measureValues(param1, param2)
def measureValues(self, param1, param2):
time.sleep(600)
# measure some things
with open(outputfile) as f:
write(things)
But that obviously used only one thread. Can you help me to find the right method to use here( QThread, QRunnable) or to map the example of the second link to my module?
Thanks!
Sorry if the title is confusing. I'm writing a minimalist game engine, and trying to define a class called "Area" where if the player enters the area, a function defined by the user happens. For example, one could create an instance
Area(location,function) that would fire function on the player when the player enters location (for the sake of simplicity, let it be a point or something).
Note: in pseudo-python
# in init.py
...
def function(player):
kill player
deathZone = Area(location,function)
--------------------------------------
# in player.update()
...
for area on screen:
if player in area:
Area.function(player)
The point of this is that the developer (aka me) can use any function they choose for the area. Is there anyway to do this, or should I try a better approach?
Sure, this kind of thing is certainly possible. In python, everything is an object, even a function. So you can pass around a function reference as a variable. For example try the following code:
import math
def rectangle(a, b):
return a*b
def circle(radius):
return math.pi * radius**2
class FunctionRunner(object):
def __init__(self):
self.userFunction = None
self.userParams = None
def setUserFunction(self, func, *params):
self.userFunction = func
self.userParams = params
def runFunction(self):
return self.userFunction(*self.userParams)
if __name__ == '__main__':
functionRunner = FunctionRunner()
functionRunner.setUserFunction(rectangle, 6, 7)
print(functionRunner.runFunction())
functionRunner.setUserFunction(circle, 42)
print(functionRunner.runFunction())
Here you have two functions that are defined for an area, and a class called FunctionRunner which can run any function with any number of input arguments. In the main program, notice that you need only pass the reference to the function name, and any input arguments needed to the setUserFunction method. This kind of thing will allow you to execute arbitrary code on the fly.
Alternatively, you could also replace a method on your class with a reference to another function (which is what you are asking), though this seems less safe to me. But it is certainly possible. For example you could have a class like this:
class FunctionRunner2(object):
def __init__(self):
pass
def setUserFunction(self, func):
self.theFunction = func
def theFunction(self, *params):
pass
And then do this:
if __name__ == '__main__':
functionRunner2 = FunctionRunner2()
functionRunner2.setUserFunction(rectangle)
print(functionRunner2.theFunction(6,7))
functionRunner2.setUserFunction(circle)
print(functionRunner2.theFunction(42))