Need Help with OOP/my project, trouble understanding how things "connect" and work together.
-
Hey everyone,
I'm very new to PyQt5 and not that good at Python either as you will see momentarily. Let me walk you through the idea and then post my code (and this code is just a simple "sketch" so to say.. to get familiar with the different things):
You have a Main Menu. One of the buttons you can press there is the "Skill Tree"-button which opens up another window.
In there you can allocate a total of 110 skill points.
Each skill has its own button that you can press.. and by doing so, you invest 1 skill-point. So it knows which button has been pressed.It should also distinguish between left- and right-click (as left will spend points, right will subtract them) and allow shift+left/right-click to allocate or subtract 5 pts.
I can get each of these to work by themselves.. but not together (with the buttons). And that's where my lack of OOP and PyQt5 knowledge comes into play. Not really grasping when to instantiate, what to pass as parameters, if my button (which is of class QPushButton/AbstractButton) can even use the members/functions(?) of other PyQt5 Classes etc.
The Last class in the code below isn't called/instantiated btw, just to demonstrate what I'd like to do/connect.import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout from PyQt5.QtWidgets import QWidget from functools import partial class MainWindow(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setObjectName("MainWindow") self.resize(1052, 804) self.setStyleSheet("background-color: rgb(24, 23, 24)") self.button1 = QPushButton(self) self.button1.setGeometry(100, 100, 100, 100) self.button1.setStyleSheet("background-color: white") self.button1.clicked.connect(self.is_clicked) def is_clicked(self): self.r = MainWindowTwo() self.r.show() class MainWindowTwo(QMainWindow): def __init__(self): super().__init__() self.initUIB() def initUIB(self): self.setGeometry(200, 200, 800, 600) self.label = QLabel(self) self.label.move(350, 80) self.button2 = QPushButton(self) self.button2.setGeometry(100, 100, 180, 180) self.button3 = QPushButton(self) self.button3.setGeometry(300, 300, 180, 180) self.button2.clicked.connect(partial(self.clicked, "Button 2")) self.button3.clicked.connect(partial(self.clicked, "Button 3")) def clicked(self, value): if value == "Button 2": self.button2.setText("I am Button 2") if value == "Button 3": self.button3.setText("I am Button 3") def mousePressEvent(self, e): if e.button() == Qt.LeftButton: self.label.setText("Left Button pressed.") if e.button() == Qt.RightButton: self.label.setText("Right Button pressed.") class MyWidget(QWidget): def __init__(self): super().__init__() self.intUIC() def intUIC(self): self.layout = QVBoxLayout(self) self.button2 = QPushButton(self) self.button2.clicked.connect(self.handleButton) self.button2.setText("Click me!") self.layout.addWidget(self.button2) self.setLayout(self.layout) def handleButton(self): self.modifiers = QApplication.keyboardModifiers() if self.modifiers == Qt.ShiftModifier: print("shift-click") if self.modifiers == Qt.ControlModifier: print("ctrl-click") if self.modifiers == (Qt.ShiftModifier | Qt.ControlModifier): print("shift+control-click") if self.modifiers != Qt.ShiftModifier and self.modifiers != Qt.ControlModifier and self.modifiers != (Qt.ShiftModifier | Qt.ControlModifier): print("mouseclick") if __name__ == "__main__": def run(): app = QApplication(sys.argv) m = MainWindow() m.show() sys.exit(app.exec_()) run()Sorry if this post sounds a bit whiny, just a bit frustrated and I desperately want to understand all of this better and continue working on my project because it's fun.. and I want to improve.
-
Hi
A signal and slot connection are always between instantiated objects.
The sender and receiver must both be a pointer to an instance of the class.
( with the exception of lambdas )-if my button (which is of class QPushButton/AbstractButton) can even use the members/functions(?) of other PyQt5 Classes etc.
well yes they can. but it's not actually the button that needs access, its more the function (slot) you connected to its signal.But here is Qt signal & slot concept very handy.
You can connect it's signal to more than one slot. Also to a slot in another instance of another class.
You can also easily add a new signal to a class. Like HitPointChanged and use that signal
to connect other instances of classes that need to know this.
Then you simply emit this new signal and others will know.This allows each class to know very little about others and still "talk".
-
Hi
A signal and slot connection are always between instantiated objects.
The sender and receiver must both be a pointer to an instance of the class.
( with the exception of lambdas )-if my button (which is of class QPushButton/AbstractButton) can even use the members/functions(?) of other PyQt5 Classes etc.
well yes they can. but it's not actually the button that needs access, its more the function (slot) you connected to its signal.But here is Qt signal & slot concept very handy.
You can connect it's signal to more than one slot. Also to a slot in another instance of another class.
You can also easily add a new signal to a class. Like HitPointChanged and use that signal
to connect other instances of classes that need to know this.
Then you simply emit this new signal and others will know.This allows each class to know very little about others and still "talk".
@mrjj Hey, first off I'd like to say thank you for typing all this out!
Now.. I want to say I understand what you're telling me, but if you're asking me to now go ahead and implement this new knowledge and edit my code so it all works, I wouldn't be able to do it.
That's not to say your explanation is bad in any way, it's just that (as you can probably tell) I'm still pretty new to OOP & PyQt5 and I have no idea how to even approach this.
I watched several OOP-tutorials and can do the stuff they always show off in these (most of them show the Class Person example with multiple other Classes (Manager, Employee etc.) inheriting from Person.. all its methods and properties). Simple enough.
But when I look at this and try to rewrite my code so it works... or think about how I'd code what you're telling me, I'm just lost & scratching my head, honestly. Sorry
-
@mrjj Hey, first off I'd like to say thank you for typing all this out!
Now.. I want to say I understand what you're telling me, but if you're asking me to now go ahead and implement this new knowledge and edit my code so it all works, I wouldn't be able to do it.
That's not to say your explanation is bad in any way, it's just that (as you can probably tell) I'm still pretty new to OOP & PyQt5 and I have no idea how to even approach this.
I watched several OOP-tutorials and can do the stuff they always show off in these (most of them show the Class Person example with multiple other Classes (Manager, Employee etc.) inheriting from Person.. all its methods and properties). Simple enough.
But when I look at this and try to rewrite my code so it works... or think about how I'd code what you're telling me, I'm just lost & scratching my head, honestly. Sorry
Hi
Thats ok.
But what exactly do you want to happen?Which class has the skillpoint list ?
For the case of add/reducing skill point assignment for a player, I assume you
must read the current assignment from some class and add/reduce from it and the write it back.Im asking as the data class is the one that is normally shared amount classes/GUI or being hold by say
MainWindow and other classes use its slots to manipulate its data. -
Hi
Thats ok.
But what exactly do you want to happen?Which class has the skillpoint list ?
For the case of add/reducing skill point assignment for a player, I assume you
must read the current assignment from some class and add/reduce from it and the write it back.Im asking as the data class is the one that is normally shared amount classes/GUI or being hold by say
MainWindow and other classes use its slots to manipulate its data.@mrjj Hey,
I actually don't know how I'm going to structure/build all of this yet as I'm just experimenting with the different PyQt5 possibilities and learning about certain functionalities which I'm going to need for my project later on.Right now (for the purpose of learning this), I just want to keep the code as is and manipulate/connect the few methods and attributes the classes/instances have.
So for example, after testing some more.. I figured out that if I make these changes to the above code:
old:
def clicked(self, value): if value == "Button 2": self.button2.setText("I am Button 2") if value == "Button 3": self.button3.setText("I am Button 3")new:
def clicked(self, value): t = MyWidget() if value == "Button 2": t.handleButton() self.button2.setText("I am Button 2") if value == "Button 3": self.button3.setText("I am Button 3") t.handleButton()it will correctly check if either of the 2 buttons in the MainWindowTwo class have been pressed with any of the defined modifiers in MyWidget's handlebutton() method (shift, ctrl, shift+ctrl) and no longer if only the Widget itself has been clicked like that.
But if I try the same with mousePressEvent (whether it's in the MainWindowTwo Class or MyWidget Class), I get the Error: 'QPushButton' object has no attribute 'button'
or
'MainWindowTwo' object has no attribute 'button'depending on how I call it.
I'm sure this seems very silly, I'm sorry. I just want it to also check if the two buttons which are defined in MainWindowTwo have been pressed with either left- or right-click.
-
@mrjj Hey,
I actually don't know how I'm going to structure/build all of this yet as I'm just experimenting with the different PyQt5 possibilities and learning about certain functionalities which I'm going to need for my project later on.Right now (for the purpose of learning this), I just want to keep the code as is and manipulate/connect the few methods and attributes the classes/instances have.
So for example, after testing some more.. I figured out that if I make these changes to the above code:
old:
def clicked(self, value): if value == "Button 2": self.button2.setText("I am Button 2") if value == "Button 3": self.button3.setText("I am Button 3")new:
def clicked(self, value): t = MyWidget() if value == "Button 2": t.handleButton() self.button2.setText("I am Button 2") if value == "Button 3": self.button3.setText("I am Button 3") t.handleButton()it will correctly check if either of the 2 buttons in the MainWindowTwo class have been pressed with any of the defined modifiers in MyWidget's handlebutton() method (shift, ctrl, shift+ctrl) and no longer if only the Widget itself has been clicked like that.
But if I try the same with mousePressEvent (whether it's in the MainWindowTwo Class or MyWidget Class), I get the Error: 'QPushButton' object has no attribute 'button'
or
'MainWindowTwo' object has no attribute 'button'depending on how I call it.
I'm sure this seems very silly, I'm sorry. I just want it to also check if the two buttons which are defined in MainWindowTwo have been pressed with either left- or right-click.
-
@Romeoo
mousePressEvent()is an event, not a signal, and has to be coded quite differently. Show your code which errors if you want help.@JonB Hey, thanks!
I already posted the code in my first post though?
The Errors I mentioned like "QPushButton' object has no attribute 'button'" was just me messing around with the code and trying stuff out.. just mentioning nonsense I tried.I have not found a way to do what I want. The code in my initial post is the code I have and it works/has no Error messages because I have no idea how to do what I'm asking for here (which is connecting it all to work together).
That is why I made this post, so someone can hopefully show me how to do it and explain it.
Again: I want mousePressEvent() to be able to determine if self.button2 and self.button3 have been pressed with a left-click or a right-click.
Right now all it can do is determine that if I press anywhere in MainWindowTwo's Window, but not if I press on either of the buttons. -
@JonB Hey, thanks!
I already posted the code in my first post though?
The Errors I mentioned like "QPushButton' object has no attribute 'button'" was just me messing around with the code and trying stuff out.. just mentioning nonsense I tried.I have not found a way to do what I want. The code in my initial post is the code I have and it works/has no Error messages because I have no idea how to do what I'm asking for here (which is connecting it all to work together).
That is why I made this post, so someone can hopefully show me how to do it and explain it.
Again: I want mousePressEvent() to be able to determine if self.button2 and self.button3 have been pressed with a left-click or a right-click.
Right now all it can do is determine that if I press anywhere in MainWindowTwo's Window, but not if I press on either of the buttons.Hi
For MousePressEvent you must subclass the button.
something like. (disclaimer. c++ dude. don't know python :)class MyButton(QtGui.QPushButton): def mousePressEvent(self, event): // use events button() == Qt::RightButton to know if left or right QtGui.QPushButton.mousePressEvent(self, event) // call the base class which you must do or button stops to function -
Hi
For MousePressEvent you must subclass the button.
something like. (disclaimer. c++ dude. don't know python :)class MyButton(QtGui.QPushButton): def mousePressEvent(self, event): // use events button() == Qt::RightButton to know if left or right QtGui.QPushButton.mousePressEvent(self, event) // call the base class which you must do or button stops to function -
@mrjj ty for being so patient with me lol,
hmn, if I try to implement that and mess around with it, it just says
"AttributeError: module 'PyQt5.QtGui' has no attribute 'QPushButton'"AttributeError: module 'PyQt5.QtGui' has no attribute 'QPushButton'
Then it lives in another module, would be my guess.
PyQt5 is not the official python binding so not sure where to look it up.
Hmm
I saw code doing
self.pushButton = QtWidgets.QPushButton(self.centralwidget)so it lives in QtWidgets, I guess ?
so replace QtGui with QtWidgets and see if it eats it :)
-
"AttributeError: module 'PyQt5.QtGui' has no attribute 'QPushButton'
Then it lives in another module, would be my guess.
PyQt5 is not the official python binding so not sure where to look it up.
Hmm
I saw code doing
self.pushButton = QtWidgets.QPushButton(self.centralwidget)so it lives in QtWidgets, I guess ?
so replace QtGui with QtWidgets and see if it eats it :)
@mrjj thanks... been trying everything since your post, but I'm clearly way in over my head and seemingly have no idea what I'm doing.
The "best" I was able to do was create new buttons in self.button2 and self.button3 that would properly respond to the mouseclick?!
I eventually just ended up throwing lots of nonsense syntax at the wall and hope something would stick... that's how I ended up with:
self.button2.clicked.connect(MyButton(self.button2).mousePressEvent)
self.button3.clicked.connect(MyButton(self.button3).mousePressEvent)which like I said simply resulted in creating 2 additional buttons inside of my other 2 (one in self.button1 and one in self.button2) which then determined if it was left or rightclick.
Everything else just resulted in:
AttributeError: 'QPushButton' object has no attribute 'button'
AttributeError: 'PyQt5.QtCore.pyqtBoundSignal' object has no attribute 'button'
AttributeError: 'MainWindowTwo' object has no attribute 'button'I mean.. I understand these Errors. But I also have no clue how to fix this.
Anyway, sorry for wasting your time. thanks for trying to help me ; ) -
@mrjj thanks... been trying everything since your post, but I'm clearly way in over my head and seemingly have no idea what I'm doing.
The "best" I was able to do was create new buttons in self.button2 and self.button3 that would properly respond to the mouseclick?!
I eventually just ended up throwing lots of nonsense syntax at the wall and hope something would stick... that's how I ended up with:
self.button2.clicked.connect(MyButton(self.button2).mousePressEvent)
self.button3.clicked.connect(MyButton(self.button3).mousePressEvent)which like I said simply resulted in creating 2 additional buttons inside of my other 2 (one in self.button1 and one in self.button2) which then determined if it was left or rightclick.
Everything else just resulted in:
AttributeError: 'QPushButton' object has no attribute 'button'
AttributeError: 'PyQt5.QtCore.pyqtBoundSignal' object has no attribute 'button'
AttributeError: 'MainWindowTwo' object has no attribute 'button'I mean.. I understand these Errors. But I also have no clue how to fix this.
Anyway, sorry for wasting your time. thanks for trying to help me ; )@Romeoo
Hi
Ahh, i see where the confusion comes from.
https://doc.qt.io/qt-5/eventsandfilters.htmlEvents are not a signal. you cannot connect to them.
events are handled by classes in a virtual function.
and to customize that we create a sub class so its our own class.so self.button2.clicked.connect(MyButton(self.button2).mousePressEvent)
will not work.You should create instances of MyButton and use instead of the std Buttons.
Qt will then call your mousePressEvent directly in the sub class.Often one does not wish to handle the processing directly in the mousepRessEvent but
tell someone else to do it. that we can do with a new signal to keep the MyButton clean and separate from rest of the app.
Disclaimer: I'm just grabbing code from the net. i cannot test it so will have errors:)class MyButton(QtWidgets.QPushButton): leftclicked=pyqtSignal() // define new signal rightclicked=pyqtSignal() def mousePressEvent(self, event): // use events button() == Qt.RightButton to know if left or right if ( event.button == Qt.RightButton ) self.rightclicked.emit() // send signal if ( event.button == Qt.LeftButton ) self.leftclicked.emit() QtWidgets.QPushButton.mousePressEvent(self, event) // call the base class which you must do or button stops to function[edit: fixed wrong module and syntax SGaist]
then from outside, you connect, your instance of MyButton and its new signal leftclicked and rightclicked to slots in main. there you then to the actual skill assignment.