2

I have a PySide application that I'm investigating making compatible with PyQt4. Obviously this shouldn't be too difficult, but I'd like to minimise the number of code changes necessary. The biggest difference I've come across (which affects my program) is the fact that PyQt does not wrap the QUiLoader class.

Now I realise that I can still load ui files with the uic module in PyQt, however I'm currently using a subclass of QUiLoader in PySide that implements some custom functionality that I'd like to reserve if possible.

The functionality is as follows. I've subclassed QUiLoader and overridden the createWidget() method. My new implementation allows you to dynamically register widget promotions at application runtime. I can do this because the createWidget() method is responsible for instantiating the widgets from the ui file, after which (I believe) the standard QUiLoader implementation applies the specified properties from the ui file.

My Question is, is there a similar method I could override in PyQt to dynamically set widget Promotion at runtime, rather than having to set it statically through Qt Designer and the ui file.

P.S. I'm looking for a solution that involves minimal change to my existing code. For instance if I could replace my PySide QUiLoader subclass with a class that provides equivalent behaviour under PyQt, that would be ideal.

EDIT: The implementation would be used something like:

loader = UiLoader()
loader.registerCustomPromotion("myWidgetNameInQtDesigner",ClassToPromoteTo)
ui = loader.load('myUiFile.ui')

This is effectively what I have made with my subclass of QUiLoader in PySide. Any widget in the .ui file with the name "myWidgetNameInQtDesigner" would be instantiated as an instance of ClassToPromoteTo.

three_pineapples
  • 11,579
  • 5
  • 38
  • 75
  • Have a look at this http://stackoverflow.com/a/14894550/674475 . – Fenikso Jan 09 '14 at 14:25
  • There is a code which does it other way around, eg. PyQT -> PySide [here](https://github.com/lunaryorn/snippets/tree/master/qt4/designer). Maybe you will find it helpful. – Fenikso Jan 09 '14 at 14:33
  • @Fenikso Similar PySide code is what inspired me to write my own subclass of `QUiLoader` in PySide which allows dynamic promotion. I probably should have been clean that it allows dynamic promotion of **any** widget in your ui file to your own subclass of that widget. The PyQt code you linked me to only allows this for the toplevel widget with no parent (as far as I can see). – three_pineapples Jan 09 '14 at 23:49
  • @three_pineapples. Do you actually _need_ to dynamically promote _any_ widget? What is the use-case for this? And, more generally, what is your use-case for dynamic promotion at runtime? It would be useful if you could give a basic, working example that illustrates the minimum functionality that you actually _need_. – ekhumoro Jan 10 '14 at 00:03
  • @ekhumoro I am dynamically promoting widgets in my code. Could I rewrite my code so I didn't need to? Probably, but it would be a fair bit of work that I'd like to avoid. As far as I can see, I'd have to instantiate the custom widgets in Python code, apply any properties I had set for the base widget in Qt Designer, Delete the base instance from the ui file, and then add the custom widget instance to whatever layout/widget it used to be assigned to in Qt Designer. Again, obviously possible but I'd rather try and keep my current architecture **if I can**. – three_pineapples Jan 10 '14 at 01:21
  • @three_pineapples. What I'm not understanding, is why you can't promote widgets within Qt Designer. That seems an unreasonable condition. – ekhumoro Jan 10 '14 at 01:41
  • @ekhumoro Imagine 90% of two UI's (or parts of UI's) are common. With dynamic promotion at run time, you only need one UI file and you can change that 10% at runtime. When you want to make a common change to those two (parts of) UIs, you only have to edit one file. If you want to adjust a property of the base class for both instances, you can do that through the ui file. You can promote widgets using Qt designer as well (that functionality has it's use case) but promoting widgets in Qt designer means that two instances of the UI will be identical. – three_pineapples Jan 10 '14 at 02:18
  • @ekhumoro as far as I'm aware, you can't promote a widget in Qt designer so that when loaded the first time it uses custom widget x and when loaded the second time it uses custom widget y. That sort of information seems like it must be provided at the time the ui file is loaded into python, not before. – three_pineapples Jan 10 '14 at 02:20
  • @three_pineapples. Well, that remains to be seen. Who knows what hackery is possible... – ekhumoro Jan 10 '14 at 02:37

1 Answers1

1

This turns out to be quite easy, so long as you are willing to promote the relevant widgets in Qt Designer.

The idea is to add a dummy module to sys.modules and then dynamically modify the custom widget classes it contains.

So if the "Header file" was set to "mylib.dummy" when promoting widgets in Qt Designer, you would do something like this when loading ui files with PyQt:

from types import ModuleType

# dummy module
module = sys.modules['mylib.dummy'] = ModuleType('dummy')

if mode == 'FOO':
    module.TextEdit = FooTextEdit
    module.LineEdit = FooLineEdit
    module.PushButton = FooPushButton
elif mode == 'BAR':
    module.TextEdit = BarTextEdit
    module.LineEdit = BarLineEdit
    module.PushButton = BarPushButton
else:
    module.TextEdit = QTextEdit
    module.LineEdit = QLineEdit
    module.PushButton = QPushButton

# loadUi/loadUiType will add the following import line:
# from mylib.dummy import TextEdit, LineEdit, PushButton
WidgetUI = uic.loadUiType('mywidget.ui'))[0]
ui = WidgetUI()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thanks, I think that will do. My PySide implementation did it without any promotion in Qt Designer (which is useful for widgets like `QMainWindow` which can't be promoted in QtDesigner), but I think the only way to do that in PyQt is to fork the PyQt4.uic module and modify it's behaviour (which I don't particularly want to do!). Unfortunately I can't make it work when the module is called `mylib.dummy` but it does when it is called mylib (`uic` raises an import error). Any ideas (not that it is terribly important)? – three_pineapples Jan 11 '14 at 23:06
  • @three_pineapples. I did test my solution with an equivalent package structure to the one above, and the imports worked okay for me. The structure was simply: `main.py`, `lib/__init__.py`, `lib/app.py` and `lib/classes.py`. As for forking uic: I can fully understand why you would want to avoid that - a maintenance nightmare if ever there was one! – ekhumoro Jan 12 '14 at 00:03
  • Ah, I see. If you use `mylib.dummy` you actually have to have the file/folder structure for the `mylib` module. If you just have 'mylib', you can get away without creating any files. Cheers! – three_pineapples Jan 12 '14 at 00:10