5

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.

  1. 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'
  1. 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'
  1. 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

MagSec
  • 346
  • 4
  • 17
  • I'm not sure I understand but why don't you use a factory pattern? – matino Apr 02 '15 at 12:37
  • Why not just define two different versions of `UiMainBase`, each with the correct parent, then choose the correct one as the parent for `MyGui`? – chepner Apr 02 '15 at 12:41
  • @chepner: please read my edit. I hope that makes it a bit more clear why I trying to do this in this way – MagSec Apr 02 '15 at 12:58
  • @matino: The decorator variant I tried is a kind of Factory Pattern method, but I don't want to call a method to get my class. I want to that the class could be imported and set as a base in every gui class I am using. I just give it a pearent and it automatically changes/or recreate itsel with the needed base. – MagSec Apr 02 '15 at 13:05
  • @MagSec. Sorry, but this is even harder to follow than your [previous question](http://stackoverflow.com/q/29368372/984421) on this topic. However, one obvious code-smell is that all your examples import both PyQt *and* PySide. Any application/library that does that is fundamentally broken right from the start. The place to choose the GUI toolkit is at *import time*, not at *class creation time*. AFAICT, what you really want is a toolkit abstraction layer: i.e. something like [QtPy](https://pypi.python.org/pypi/QtPy/). – ekhumoro Apr 02 '15 at 19:46
  • @ekhumoro: I only import both in my example to show what I mean. I just trying to generate a library for my pipeline which I could use in any application. I will never import both packages, I just needed an example code to show what I mean. You could also create two different classes and try two do this instead of using PyQt and PySide if you feel better – MagSec Apr 02 '15 at 20:29
  • @MagSec. Well, thing is, I still don't understand what "this" is. It seems obvious why the third example doesn't work, though. The decorator has already replaced `UiMainBase` with `ParentUI` by the time `PARENT` is reset, so it couldn't possibly have any affect on the creation of the *class* (or any instances created from it). But anyway: why doesn't a toolkit abstraction layer solve your problem? How would your solution differ from it? – ekhumoro Apr 02 '15 at 23:23
  • Does this answer your question? [How to dynamically change base class of instances at runtime?](https://stackoverflow.com/questions/9539052/how-to-dynamically-change-base-class-of-instances-at-runtime) – Jonathan Komar Jan 03 '22 at 11:52

1 Answers1

2

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)
Neil G
  • 32,138
  • 39
  • 156
  • 257
chepner
  • 497,756
  • 71
  • 530
  • 681
  • That's what I was trying to do, but as a decorator. – MagSec Apr 02 '15 at 13:32
  • You don't want a decorator, since you don't want to edit an existing class; you want to define the class with the correct base in the first place. The difference is `decorator(Class(FixedBase))` (changing the grandparent after the fact) vs `Class(BaseFactory(CorrectGrandparent))` (using the right grandparent from the beginning). – chepner Apr 02 '15 at 13:36
  • A decorator could replace the class you are calling. That's what my decorator is doing. It is just not taking the updated constant variable `PARENT`. If you change inside the decorator `GetUiObject` the first line to `parentWidget = PySideGui.QMainWindow` and put the import of `PySideGui` on the top of the module, you will see that the print of `QMenuBar` is now a PySide object. So it is replacing the class itself with a new class and the specified base – MagSec Apr 02 '15 at 13:44