Do class in python need parameter when we call it? - python-3.x

I am new to python, actually new to programming too, and I am recently working on a simple project with Tkinter. In this project, I was trying to create a force attention window and I finally got the answer from this page. The main codes are as follows:
import tkinter as tk
class App(tk.Tk):
TITLE = 'Application'
WIDTH, HEIGHT, X, Y = 800, 600, 50, 50
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="open popout 1", command=self.open1).grid()
tk.Button(self, text="open popout 2", command=self.open2).grid()
def open1(self):
PopOut1(self)
def open2(self):
# .transient(self) ~
# flash PopOut if focus is attempted on main
# automatically drawn above parent
# will not appear in taskbar
PopOut2(self).transient(self)
if __name__ == '__main__':
app = App()
app.title(App.TITLE)
app.geometry(f'{App.WIDTH}x{App.HEIGHT}+{App.X}+{App.Y}')
# app.resizable(width=False, height=False)
app.mainloop()
It worked though, one thing that I am still concerning is that why he specified a parameter in a class APP:
class App(tk.Tk):
However, there is nothing pass in the class when he called it:
app = App()
Can anyone answer it, or just give me some keyword that I can go search specifically. I have read some of the tutorials about class in python, but none of them mention it.

This is called inheritance. All parameters for creating an object of the class, like you did here: app = App(), are in the __init__ method.
class App(tk.Tk):
This part is not a parameter. Instead, this indicates that the App class inherits methods from the tk.Tk class. In essence, this is what turns your App class into a Tkinter application. Without it, you wouldn't have any of the functionality that Tkinter provides. Observe how at the bottom of your code, you create the app and then call app.mainloop(). Note that your App class has no mainloop method. It actually comes from the inherited tk.Tk class.
That said, this is a major topic in most languages so I don't doubt you'll find tons of resources to learn further if you simply search for "Python inheritance".

Related

How Can I Display a Message From a ROS2 Publisher In a KivyMD Window?

What I'm trying to do is essentially take example code to set up and run a Subscriber and Publisher using ROS2 (found Here) and set up the Subscriber python script to use KivyMD to display the Message that it receives from the Publisher python script by updating a simple MDLabel text every second with a variable that the Subscriber callback updates (Note: I currently don't have the code trying to do this yet, as my issue doesn't pertain to it at the moment).
I have no idea what the 'best practice' for going about this would be, since I looked but couldn't find anybody who had done this, aside from some YouTube videos of someone doing it with ROS, but I need to use ROS2, and his tutorial doesn't help with that. So I'm pretty much just winging it.
The problem that I'm finding when trying to do this is that they both work, per se, but only one can work at a time, it seems? If in my script, at the end, I run main() (ROS2 code) before MainApp() (KivyMD code), then when I run both the Subscriber and Publisher files in separate terminals, the ROS2 functionality works fine. The Publisher's message reaches the Subscriber. However, the KivyMD window that pops up doesn't populate with the message.
The reverse is true as well, in that if I switch the position of main() and MainApp() and run the KivyMD code before the ROS2 code, then the KivyMD window shows up and populates with the placeholder text (found in .kv file), but the Subscriber doesn't hear the Publisher.
The issue might be obvious but I just can't see it, and I might have been working on this problem for too long to realize it. Can anyone help out?
Here's the python script for the Subscriber Node:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.clock import Clock
global textOutput
textOutput = ""
class MainApp(MDApp):
def on_start(self):
Clock.schedule_interval(self.update_text, 1)
def build(self):
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "BlueGray"
return Builder.load_file('/home/cobot/dev_ws/src/py_pubsub/py_pubsub/ros_gui.kv')
def update_text(self, event):
global textOutput
self.root.ids.textOutputDisplay.text = textOutput
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
global textOutput
textOutput = 'Test'
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
# minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
MainApp().run()
And here's the code for the .kv file I'm using for KivyMD:
MDScreen:
MDBoxLayout:
padding: dp(4), dp(4)
spacing: dp(4)
MDLabel:
id: textOutputDisplay
text: 'Output Text'
font_style: 'H1'
halign: 'center'
I recently had a similar problem as you, but I was using Tkinter instead of Kivy.
It seems like when you call the main() function, the program loops inside the rclpy.spin() function, constantly listening for new messages. Thus, your Kivy interface doesn't get updated.
The other way around, when you exchange the place of the main() call and the MainApp().run() call. In this case, the Kivy interface starts first and listens for user input or output, hence the thread is blocked and the ROS2-functionality doesn't start.
What might help is running both calls parallel in separate threads.
The easiest way would be to import the threading package:
import threading
Then, you can start a new thread for the ROS2-function and start the Kivy-interface afterwards in the main thread as usual:
process_thread = threading.Thread(target=main)
process_thread.start()
MainApp().run()
It is important here to set target=main, NOT target=main(). Otherwise, you would run the main function immediately intead of passing it to threading.Thread.
Hope, I could help!

Pyside2 trigger messagebox from button click closes whole application

i want to trigger a messagebox from a button click without closing the entire application, I managed to do this in my previous project, but this time, the behavior is really unexpected. as soon as I clicked ok or the cross sign on the messagebox, the app also closing too without any error. here's the minimal example
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.set_and_load_ui()
def set_and_load_ui(self):
self.ui = QUiLoader().load('main.ui', self)
def trigger_messagebox(self):
self.messagebox()
def messagebox(self,x=""):
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Information)
msg.setText("{}".format(x))
msg.setInformativeText("test")
msg.setWindowTitle("test")
msg.exec_()
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.ui.show()
# mainWindow.trigger_messagebox() #this is fine
mainWindow.ui.mainmenuButton.clicked.connect(lambda: mainWindow.trigger_messagebox()) #this is not fine
exit_code = (app.exec_())
just to check, if the messagebox itself is causing the problem, I tried to call it directly on my code, but turns out it just fine, but not when I tried to trigger it by a button click
The problem is related to the fact that QUiLoader loads the UI using the parent given as argument, and since the UI is already a QMainWindow, you're practically loading a QMainWindow into another, which is unsupported: QMainWindows are very special types of QWidgets, and are intended to be used as top level widgets.
It's unclear to me the technical reason for the silent quit, but it's certainly related to the fact that the loaded window actually has a parent (the MainWindow instance), even if that parent is not shown (since you're showing the ui).
Unfortunately, PySide doesn't provide a way to "install" a UI file to an existing instance unlike PyQt does (through the uic.loadUi function), so if you want to keep using PySide there are only two options:
do not use a main window in Designer, but a plain widget ("Widget" in the "New Form" dialog of Designer), load it using QUiLoader and use setCentralWidget() (which is mandatory, since, as the documentation also notes, "creating a main window without a central widget is not supported"):
self.ui = QUiLoader().load('main.ui', self)
self.setCentralWidget(self.ui)
The downside of this approach is that you cannot use the main window features anymore in Designer (so, no menus, status bar, dock widgets or toolbars).
use pyside-uic to generate the python code and use the multiple inheritance method to "install" it; the following assumes that you exported the ui file as mainWindow.py:
from mainWindow import ui_mainWindow
class MainWindow(QtWidgets.QMainWindow, ui_mainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
# no "set_and_load_ui"
self.mainmenuButton.clicked.connect(self.trigger_messagebox)
# ...
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
exit_code = (app.exec_())
As you can see, now you can access widgets directly (there's no ui object). The downside of this approach is that you must remember to always generate the python file everytime you modify the related ui.

Cannot execute custom code on pressure of Qt button

In QT Designer I have defined a button loadValues_btn and associated the signal pressed() to the custom slot loadValues()
Then in Python, after having loaded the .ui file with the PyQt library, I have defined the method loadValues() in the MainApplication class, in order to execute custom code when the same button is clicked. But it does not execute the code (I need simply to open a File chooser dialog)
I have already tried with clicked() and it didn't work either
Here is the MainApplication class code:
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, ):
super(Main, self).__init__()
self.setupUi(self)
def loadValues():
filePath = QtGui.QFileDialog.getOpenFileName(parent=self, caption='Open file', directory='.')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
Thank you all for your support. Actually the File dialog was not appearing simply because... it was behind the Spyder editor window!! So it was hidden and it did not pop up in the main window of the Python application I was developing!!! :( Useless to tell you that I have lost one day to understand where the problem was, f***ing Python!!!

PyQt4: Where can you make menus?

I'm using Python 3 and PyQt4
I'm trying to make a simple main window with a menubar. It doesn't work if I try to set up the menubar in the MainWindow initialization but does work if I set it up in some external function. That is, the following does NOT work:
import sys
from PyQt4 import QtGui
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
self.setMenuBar(menubar)
self.setCentralWidget(centralWidget)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
mainWindow.show()
sys.exit(app.exec_())
While if I simply move the menu setup down to the main routine:
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mainWindow = MyMainWindow()
centralWidget = QtGui.QWidget()
menubar = QtGui.QMenuBar()
menu = QtGui.QMenu("File")
menu.addAction("New")
menubar.addMenu(menu)
mainWindow.setMenuBar(menubar)
mainWindow.setCentralWidget(centralWidget)
mainWindow.show()
sys.exit(app.exec_())
Then the menu is created (I know I don't have any actions hooked up, this is stripped down to show the oddity). I thought it might have to do with being in init but moving it to another class routine, e.g. setup(self), and then calling that after creating the mainWindow doesn't solve the problem.
I seem to be continually baffled by what works and what doesn't in PyQt4. If anyone could point me at some good resources I'd also appreciate it (I've read Rapid GUI Programming but find I'm still lost).
Thanks in advance for your help
PyQt can be confusing because there are two object ownership models in use together. Here for example you are creating a QMenu which can be owned either by your Python program or by the Qt object hierarchy.
QMenus have two typical use cases: in one you create a pop-up menu and use QMenu.exec_ to await a response modally. In this case you naturally "own" the menu: you will hold a reference to it until it has finished and closed.
But your code illustrates the other case. Here you have no interest in holding a reference to the object: you have added one just as a workaround. But you don't need to do this.
Qt has its own object ownership hierarchy which typically starts at the top-level windows which have no parent and then goes down through all their component widgets. This parenting is typically established by assigning the parent upon creation of each child QObject. Once this is done, the object's lifetime is tied to that of their parent unless they are explicitly deleted sooner.
So that's what you want in this case:
menu = QtGui.QMenu("File",self)
The other widgets such as the central widget and the menu bar could have the same issue. But in each case they are being assigned a parent as they are being added to the main window.
Giving QObjects parents is generally the right thing to do. It soon becomes second nature to just add a parent parameter to every creation call.

PyQt4 QSpinBox.selectAll() not working as expected

Python 2.5.4
PyQt4
I sub-classed a QDoubleSpinBox to emit a signal on a focusIn event:
#Custom widgets for DPL GUI
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class DPLDoubleSpinBox(QDoubleSpinBox):
__pyqtSignals__ = ("valueChanged(double)", "focusIn()")
def __init__(self, *args):
QDoubleSpinBox.__init__(self, *args)
def event(self, event):
if(event.type()==QEvent.FocusIn):
self.emit(SIGNAL("focusIn()"))
#self.clear() Works as expected
self.selectAll() #See below
return QDoubleSpinBox.event(self, event)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
widget = DPLDoubleSpinBox()
widget2 = DPLDoubleSpinBox()
widget.show()
widget2.show()
sys.exit(app.exec_())
If you click inside one box, then kill the other window, it works. If you click inside one, then the other, then focus any other window on the desktop, it seems to work.
I think it's a focus problem, but can't track it down. I just need it to select all when clicked on. I tried doing it through its line edit pointer, but I get the same results. Tried forcing focus to other widgets, but still same result.
You can connect a custom slot to fire when it emits "focusIn()". You can then anyQSpinBox.selectAll(), and it works, just not on itself.
I know this question is more than two years old, but since it is one of the first results when googling "qspinbox select on focus", I would like to leave a solution for future generations.
The problem is the behavior of the QSpinBox.lineEdit(). With the focusInEvent, you can call selectAll(), but for some reason, the mousePressEvent of QLineEdit clears the selection right after the focus event. See here for an explanation.
The solution is to install an event filter for the QSpinBox.lineEdit() widget or subclass QLineEdit and call QSpinBox.setLineEdit(). Either way, the link above will show you how to achieve the desired behavior by keeping a boolean flag around and filtering both focusInEvent and mousePressEvent.
According to this, put a QTimer.singleShot call to selectAll inside the overriden focusInEvent, and then magic happens.
class SpinBox(QSpinBox):
def focusInEvent(self, event: QFocusEvent) -> None:
QTimer.singleShot(0, self.selectAll)
or like this (not recommended):
b = QSpinBox()
b.focusInEvent = lambda _: QTimer.singleShot(0, b.selectAll)
I changed the event to QEvent.Enter
Now it will self.selectAll()
I can get away with this because it's for a touch screen application, so it wouldn't be obvious to the user that something is amiss. I'd still love to know what I'm missing, or if this is just a bug.

Resources