I want to split one big class (UI) into several smaller ones and I have a problem with accessing the other fields in the rest of GUI.
I created a simple GUI using QT Creator and now I want to program it using PyQt5.
When I split the functions into separate classes and use the verifyBtn button I get an error:
self.hostname = self.IPInp.text()
AttributeError: 'bool' object has no attribute 'IPInp'
If verify_button function is in main UI class then program works fine. Do you have any idea how the code should look like so I can split the function into another class?
from PyQt5.QtWidgets import *
from PyQt5 import uic
import os, sys
class ButtonHandler(QMainWindow):
def __init__(self) -> None:
super().__init__()
def verify_button(self):
self.hostname = self.IPInp.text()
response = os.system("ping -n 1 " + self.hostname)
if response == 0:
self.verifyBtn.setStyleSheet('QPushButton { color: green;}')
self.verifyBtn.setText("OK")
else:
self.verifyBtn.setStyleSheet('QPushButton { color: red;}')
self.verifyBtn.setText("NO")
class UI(QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("Test_gui.ui", self)
self.vBtn = ButtonHandler()
self.verifyBtn.clicked.connect(self.vBtn.verify_button)
if __name__ == '__main__':
app = QApplication([])
win = UI()
win.show()
sys.exit(app.exec_())
In the __init__-method of ButtonHandler you can pass a reference to UI in the following way (if you use toplevel_ui for the reference to UI)
class ButtonHandler(QMainWindow):
def __init__(self, toplevel) -> None:
super().__init__()
self.toplevel_ui = toplevel_ui
so when you create an instance of ButtonHandler from UI, you now write
self.vBtn = ButtonHandler(self)
and then when you want to access properties of the class UI from the class ButtonHandler you write self.toplevel_ui.<attribute_name> where you replace <attribute_name> with whatever you want. For example verifyBtn or verify_button.
Related
Before writing the Python code, I already made a ui file to made QT designer. The UI file has one Qlabel containing the values. To output an external value, I've tried, but I never could never make it. This is the My QtCode
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
form_class = uic.loadUiType("untitled.ui")[0]
class WindowClass(QMainWindow, form_class) :
def __init__(self) :
super().__init__()
self.setupUi(self)
self.InitUI()
def InitUI(self, name):
self.hello.setText("Hello. %s"%name)
if __name__ == "__main__" :
app = QApplication(sys.argv)
myWindow = WindowClass()
myWindow.show()
app.exec_()
I want to accept the name value in if__name__=="main_" and hand it over to the part that outputs the label of the Qt class. I am not that good at English.
I am building a program to view a video and do image processing on it. I want to know the proper way to give my individual classes access to the ui elements.
The way I implemented it now is the following:
I designed a GUI in QT Creator and save the video.ui file, then I generate the Ui_video.py file from it with pyside6-uic.
My main.py then looks like this:
import sys
from PySide6.QtWidgets import QMainWindow, QApplication
from PySide6.QtCore import QCoreApplication
from ui_video import Ui_Video
class VideoViewerMain(QMainWindow):
def __init__(self):
super().__init__()
self.ui : Ui_Video = None
def closeEvent(self, event):
self.ui.video_view.closeEvent(event)
return super().closeEvent(event)
class App(QApplication):
def __init__(self, *args, **kwargs):
super(App,self).__init__(*args, **kwargs)
self.window = VideoViewerMain()
self.ui = Ui_Video() #
self.window.ui = self.ui
self.ui.setupUi(self.window)
self.window.show()
if __name__ == "__main__":
# init application
app = App(sys.argv)
app.processEvents()
# execute qt main loop
sys.exit(app.exec())
Then I implemented my video previewer, which inherits from QOpenGLWidget as a way to display the video. (I guess this could be any widget type that supports painting)
The Qt hierarchy looks like this:
In the QT Creator I set the corresponding widget as a custom class. This means my class is instantiated when the main class calls setupUI.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ui_video import Ui_Video
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, parent=None):
super().__init__(parent)
self.ui: Ui_Video = self.window().ui
self.reader = VideoReader(r"some/video/path")
# adjust the seekBar range with video length, this throws error
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
self._first_show = True
def showEvent(self, event: QtGui.QShowEvent) -> None:
if self._first_show:
self._first_show = False
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
self.ui.seekBar.setMinimum(0)
return super().showEvent(event)
When I try to initialize the ui seekBar in the init function, it throws an error
File "src/VideoProcessor\videopreview.py", line 56, in __init__
self.ui.seekBar.setMaximum(self.reader.number_of_frames - 1)
AttributeError: 'Ui_Video' object has no attribute 'seekBar'
This is because my custom class is instantiated before the seekBar in the setupUI function which I cannot change. Currently I use the workaround with the showEvent.
So my question is: How would a proper implementation of a custom widget look like in this context?
Should I divide the functionality of the video controls from the widget entirely?
How can I ensure that the classes have access to the ui elements?
The main thing I see is your use of colons in assignment statements.
In python colons are mainly used to for setting dictionary literal values...
my_dict = {"my_key1": 1, "my_key2": 2}
ending loops/condition statements...
for index in range(12):
if index == 5:
print("FIVE")
and the occasional lambda when necessary...
sorted_my_dict = sorted(my_dict.items(), key=lambda item: item[1])
In your case I see at least 2 places where you are using colons in ways I've never personally seen in python.
self.ui : Ui_Video = None
# and
self.ui: Ui_Video = self.window().ui
If you need to set a value to None just use
self.ui = None
when setting it to an existing object from another widget either utilize the parent argument or just pass the object in as a different argument...
self.ui = parent.ui
# or
class VideoPreview(QOpenGLWidget):
"""
widget to display camera feed and overlay droplet approximations from opencv
"""
def __init__(self, ui_object, parent=None):
super().__init__(parent)
self.ui = ui_object.ui
[...]
I am creating a small GUI program using PySide. I am having difficulty creating another object of same class. What exactly I am trying to do is that when clicked on a button on MainWindow it should create another independent window of same class.
import sys
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initUI()
def initUI(self):
....
self.addToolBarElements()
....
self.show()
def addToolBarElements(self):
....
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')),"New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(newwindow)
self.toolBar.addAction(self.newwindow)
def newwindow(self):
#how to create new object of same class
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
Here is what I have tried:
I have tried multiprocessing but I didn't understand much. I tried calling run() method again but it gives error.
Do not call with the same name 2 different elements, in your case self.newwindow refers to the QAction as the method of the class, avoid it, that is a type of error easy to commit but difficult to find.
going to the point, you just have to create a new object of the class, but the problem is that the garbage collector will eliminate it, to avoid it there are 2 possible options, the first is to make the new window member of the class, or second store it in a list, that's the one I choose because I think you want to have several windows.
import sys
import os
from PySide import QtCore, QtGui
class Sticky(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.others_windows = []
self.initUI()
def initUI(self):
self.addToolBarElements()
self.show()
def addToolBarElements(self):
self.toolBar = self.addToolBar("toolBar")
self.newwindow = QtGui.QAction(QtGui.QIcon(os.path.join(os.path.dirname(__file__),'icons/new.png')), "New Note",self)
self.newwindow.setStatusTip("New")
self.newwindow.triggered.connect(self.on_newwindow)
self.toolBar.addAction(self.newwindow)
def on_newwindow(self):
w = Sticky()
w.show()
self.others_windows.append(w)
def run():
app = QtGui.QApplication(sys.argv)
notes = Sticky()
sys.exit(app.exec_())
run()
I am trying to create a common class which I want to use inside different applications.
The idea: a class which could be used as a base class for ui creations. This class will be based to a widget of the applications in it's specific gui system (PySide, or PyQt)
This would allow me to follow the same code flow to generate gui's. I like to have same structures inside my pipeline and it makes it much easier to work in different applications with the same commands.
The problem: PyQt and PySide are compiled in C++ and do not follow the Python class structure
I tried a lot of different things to get it to work, but every time I got stock at some point and doesn't get the result I want.
Try (rebase):
in this try I used __class__ to rebase the class itself.
from PyQt4 import QtGui, QtCore
class UiMainBase(object):
PARENT = QtGui.QMainWindow
def __init__(self, uiFile=None, parent=None):
if parent: self.PARENT = parent
self.__class__ = self.PARENT
if __name__ == "__main__":
import sys
from PySide import QtGui as PySideGui
class MyGui(UiMainBase):
def __init__(self, uiFile):
super(MyGui, self).__init__(uiFile, PySideGui.QMainWindow)
Ui = r"C:\pythonTest\ui\test.ui"
app = PySideGui.QApplication(sys.argv)
win = MyGui(Ui)
win.show()
sys.exit(app.exec_())
The errors I get are reasonable and doesn't has to be explained.
# 1st Error :
Traceback (most recent call last):
#[..]
File "C:/pythonTest/ui/ui.py", line 18, in __init__
self.__class__ = self.PARENT
TypeError: __class__ assignment: only for heap types
# 2nd Error (without parsing the parent
# argument to __init__ of super in MyGui) :
Traceback (most recent call last):
#[..]
File "C:/pythonTest/ui/uiTest.py", line 18, in __init__
self.__class__ = self.PARENT
TypeError: __class__ assignment: 'MyGui' object layout differs from 'QMainWindow'
Try (rebase):
in this try I used __bases__ to rebase the class itself.
from PyQt4 import QtGui, QtCore
class UiMainBase(object):
PARENT = QtGui.QMainWindow
def __init__(self, uiFile=None, parent=None):
if parent: self.PARENT = parent
self.__bases__= (self.PARENT,)
if __name__ == "__main__":
import sys
from PySide import QtGui as PySideGui
class MyGui(UiMainBase):
def __init__(self, uiFile):
super(MyGui, self).__init__(uiFile, PySideGui.QMainWindow)
Ui = r"C:\pythonTest\ui\test.ui"
app = PySideGui.QApplication(sys.argv)
win = MyGui(Ui)
print win.__bases__ # <- shows what base the object has
win.show()
sys.exit(app.exec_())
In this result, we can see that the object now get's the right base, but don't get it's methods and variables (not even if I call super after setting __bases__).
<type 'PySide.QtGui.QMainWindow'> # <- the base
Traceback (most recent call last):
File "C:/pythonTest/ui/uiTest.py", line 34, in <module>
win.show()
AttributeError: 'MyGui' object has no attribute 'show'
Try (decorator) :
instead of rebase an object I tried to replace the class with another one that has the right base
from PyQt4 import QtGui, QtCore
def GetUiObject(uiClass):
parentWidget = uiClass.PARENT # <- Gets the original value,
# not the latest, why?
class ParentUI(parentWidget, uiClass):
def __init__(self, *args, **kwargs):
super(ParentUI, self).__init__()
uiClass.__init__(self, *args, **kwargs)
#[..]
def __call__(self, cls):
for func in uiClass.__dict__:
setattr(cls, func, uiClass.__dict__[func])
#ParentUI = type("ParentUI", (parentWidget,),ParentUI.__dict__.copy())
return ParentUI
#GetUiObject
class UiMainBase( object ):
PARENT = QtGui.QMainWindow
def __init__(self, uiFile=None, *args, **kwargs):
"""constructor.."""
#[..]
if __name__ == "__main__":
import sys
from PySide import QtGui as PySideGui
UiMainBase.PARENT = PySideGui.QMainWindow # <- specify the application
# ui architecture as parent
class MyGui(UiMainBase):
def __init__(self, uiFile=None):
# This variant of base class definition doesn't allow parsing
# the parent argument with with it
# (__init__ will be executed after base is set)
super(MyGui, self).__init__(uiFile=None)
print self.menuBar () # <- check used ui architecture
Th e result of this variant doesn't error and prints: <PyQt4.QtGui.QMenuBar object at 0x00000000021C9D38>, but MyGui is not based to PySideGui.QMainWindow like I expected
EDIT:
Why do not defining classes with a base of PySide and one with a base of PyQt4?:
Because I want to leave it open for a genral use and later extensions. It should be free to set a parent widget, which is defined by the application (PySide or PyQt). But each application has it's own method or function to get it's MainWindow. So if you want extend some widgets of this MainWindow, or just parent a new ui to it you need to define it as the parent, or directly base from it. Some applications and it's API's do not support a free generated ui from type PySide.QtGui.QMainWindow or PyQt4.QtGui.QMainWindow. That's the main reason I was trying to do it in this way. Of course I could create a UiMainWindow class for each application and base it to it's main window (this is what I do for now), but I was trying to make it more generic and global for my pipeline API.
Example of usage:
This example is for 3DS-Max and inside a module of it's pipeline integration.
from pythonLibrary.file import Path
from qtLibrary.ui import UiMainBase
from blurdev.gui import Window
UiMainBase.PARENT = Window
class UiWidget( UiMainBase ):
"""
This class creates a widget which is parented
to the PySide implementation of blur's Python
in 3DS-Max
"""
def __init__(self, uiFile=None):
super(UiWidget, self).__init__()
if uiFile: self.loadUi(uiFile, self)
self.show()
#[..]
class PublishUi(UiWidget):
def __init__(self):
uiFile = Path.Combine(__file__,self.__class__.__name__+".ui")
super(PublishUi, self).__init__(uiFile)
#[..]
if __name__ == "__main__":
# This should be called from a menu entry inside 3DS-Max
publishWindow = PublishUi()
Does anyone has a solution for this situation?
Cheers,
Michael
Make a factory function which dynamically generates the correct base class. In real life, you'd probably memoize the factory so that it always returns the same class object for a given parent, rather than creating multiple identical classes.
from PyQt4 import QtGui, QtCore
def BaseFactory(parent):
class UiMainBase(parent):
....
return UiMainBase
if __name__ == "__main__":
import sys
from PySide import QtGui as PySideGui
# UiMainBase = BaseFactory(QtGui.QMainWindow)
UiMainBase = BaseFactory(PySideGui.QMainWindow)
class MyGui(UiMainBase):
...
Ui = r"C:\pythonTest\ui\test.ui"
app = PySideGui.QApplication(sys.argv)
win = MyGui(Ui)
I want to use signals for communicating between my view and my application controller. I have following approach but since I'm beginner in PyQt I don't know if that is the right one. Can anyone tell me If I am on the right path or are there better solutions?
EDIT: I have changed the example to a fully working example.
import sys
from PyQt4 import QtGui, QtCore
class View(QtGui.QMainWindow):
sigFooChanged = QtCore.pyqtSignal()
sigBarChanged = QtCore.pyqtSignal()
def __init__(self):
QtGui.QMainWindow.__init__(self)
central_widget = QtGui.QWidget()
central_layout = QtGui.QHBoxLayout()
self.__cbFoo = QtGui.QComboBox()
self.__cbBar = QtGui.QComboBox()
self.__cbFoo.currentIndexChanged[str].connect(lambda x: self.sigFooChanged.emit())
self.__cbBar.currentIndexChanged[str].connect(lambda x: self.sigBarChanged.emit())
central_layout.addWidget(QtGui.QLabel("Foo:"))
central_layout.addWidget(self.__cbFoo)
central_layout.addWidget(QtGui.QLabel("Bar:"))
central_layout.addWidget(self.__cbBar)
central_widget.setLayout(central_layout)
self.setCentralWidget(central_widget)
def setFooModel(self, model):
self.__cbFoo.setModel(model)
def setBarModel(self, model):
self.__cbBar.setModel(model)
class Controller:
def __init__(self, view):
self.__view = view
# Connect all signals from view with according handlers
self.__view.sigFooChanged.connect(self.handleFooChanged)
self.__view.sigBarChanged.connect(self.handleBarChanged)
self.__fooModel = QtGui.QStringListModel(["Foo1", "Foo2", "Foo3"])
self.__barModel = QtGui.QStringListModel(["Bar1", "Bar2", "Bar3"])
self.__view.setFooModel(self.__fooModel)
self.__view.setBarModel(self.__barModel)
def handleFooChanged(self):
print("Foo Changed")
def handleBarChanged(self):
print("Bar Changed")
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
view = View()
controller = Controller(view)
view.show()
sys.exit(app.exec_())
Personally, I don't create a separate generic controller class like that. It could be my own preference, but I tend to consider the actual QWidget class my controller, and the view is usually the GUI-only definitions that I generate from QtDesigner (Ui_Dialog for example), or create manually. And I make all my connections in the relevant QWidget.
Now on to your code, I don't know if you are just considering this snippet a general pseudocode example of the direction you are taking...but it has errors... I would normally suggest posting working code so people don't get confused as to whether you are having errors because of it, or just asking if its generally a correct direction to laying out code.
You are forgetting to call __init__() on the QMainWindow superclass.
I'm not sure what controller.show() would do (fail as of right now) because I don't see an example of how you intend to forward that show() command to your main window object? Again I don't really see why its even necessary to have that separate class.
Here is how I would see a more realistic example, again considering the QWidget classes themselves to be the controllers:
View
## mainUI.py ##
from PyQt4 import QtCore, QtGui
class Ui_MyWidget(object):
def setupUi(self, obj):
obj.layout = QtGui.QVBoxLayout(obj)
obj.cbFoo = QtGui.QComboBox()
obj.cbBar = QtGui.QComboBox()
obj.layout.addWidget(obj.cbFoo)
obj.layout.addWidget(obj.cbBar)
Non-Gui Library Module (Controller)
## nonGuiModule.py ##
class LibModule(object):
def handleBarChanged(self, *args):
print("Bar Changed: %s" % args)
Controller (any entry point)
## main.py ##
import sys
from PyQt4 import QtCore, QtGui
from mainUI import Ui_MyWidget
from nonGuiModule import LibModule
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.resize(640,480)
self._lib = LibModule()
self.myWidget = MyWidget(self)
self.setCentralWidget(self.myWidget)
self.myWidget.sigFooChanged.connect(self.handleFooChanged)
self.myWidget.sigBarChanged.connect(self._lib.handleBarChanged)
def handleFooChanged(self, *args):
print("Foo Changed: %s" % args)
class MyWidget(QtGui.QFrame, Ui_MyWidget):
sigFooChanged = QtCore.pyqtSignal(str)
sigBarChanged = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
# this is where you set up from the view
self.setupUi(self)
self.cbFoo.addItems(['Foo1', 'Foo2'])
self.cbBar.addItems(['Bar1', 'Bar2'])
self.layout.addWidget(self.cbFoo)
self.layout.addWidget(self.cbBar)
# going to forward private signals to public signals
self.cbFoo.currentIndexChanged[str].connect(self.sigFooChanged)
self.cbBar.currentIndexChanged[str].connect(self.sigBarChanged)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv[1:])
view = Main()
view.show()
sys.exit(app.exec_())