Runtime UI class loading - why?
The "qtui" module lets you read a UI file at runtime, and create an instance of the widget it describes (which is usually a QMainWindow or QDialog).
This is useful, because it avoids having to run pyuic at compile time, and allows your programs to choose and load UI files dynamically. However, in Python it's more useful to be able to create the class of the widget, rather than an instance of that class, at runtime. There are two big advantages:
- You can do multiple inheritance where one of the parent classes is in fact a UI file chosen at runtime. (The other parent class might contain generic application logic, for example).
You avoid the manual compile step (.ui --> .py), allowing the "edit -- run" development style which is normal with python scripts.
Neither of the above make any sense in a C++ context, which is why qtui doesn't implement this.
Here is a module which allows such runtime loading. It works by spawning pyuic and then using the import internals.
Example Usage
import PyUIC
FormClass = PyUIC.load('form.ui')
Bugs, Limitations
- Currently, each UI file has to be processed by pyuic, and then by python, every time it is loaded. Performance could be enhanced by cacheing generated .py or .pyc files. These could be stored in a special directory, and files could be named by md5sum of the UI file.
- It is assumed that the name of the single class described in a UI file occurs on a line in the output of pyuic which begins "class". It is conceivable that a newer version of pyuic could break this.
PyUIC.py
"""Loads Qt UI files as python classes at runtime.
EXAMPLE USAGE
import qt
import PyUIC
# Load screen class from UI file
FormClass = PyUIC.load('exampleform.ui')
REQUIRES
- Qt
- PyQt
- pyuic
COPYRIGHT
Copyright (C) 2004, David Chan and Clockwork Software Systems.
This program is free software; you can distribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
"""
import os
import re
import imp
import popen2
class Error(Exception):
pass
def load(filename):
"""Reads a UI file; returns a python class"""
python_code = _compile_ui(filename)
module = _load_module(python_code, filename)
klass = _load_class(module)
return klass
def _compile_ui(filename):
"""Reads a UI file; returns a string of python code"""
# Create new process, get its stdin/stdout
uic_out, uic_in = popen2.popen2('pyuic /dev/stdin')
# Write UI file to pyuic
ui_file = open(filename)
uic_in.write(ui_file.read())
uic_in.close()
# Read code from pyuic -> python_code
python_code = uic_out.read()
uic_out.close()
return python_code
# -----------------------------------------------------------------
def _load_module(python_code, filename):
"""loads the code as a python module"""
# Read class name from code
m = re.search('^class\s+(\w+)', python_code, re.MULTILINE)
if not m:
raise Error, "Can't find class name:((%s))" % python_code
class_name = m.group(1)
# Write code to a temporary file
tmp_file = os.tmpfile()
tmp_file.write(python_code)
tmp_file.seek(0)
# Load code with the module loader
if not imp.lock_held(): imp.acquire_lock()
try:
module = imp.load_source(class_name, filename, tmp_file)
finally:
tmp_file.close()
imp.release_lock()
# Delete compiled .XXc file XXX can we stop it being made?
try:
os.unlink(filename + 'c')
except OSError, ex:
pass
return module
# -----------------------------------------------------------------
def _load_class(module):
"""Returns the class whose name the module shares"""
try:
klass = getattr(module, module.__name__)
except AttributeError:
raise Error, "Can't get class %r" % class_name
return klass
# ---------------------------------------------------------------------
#
# Test code
#
if __name__ == '__main__':
import qt
import sys
if len(sys.argv) != 2:
print "Test usage: %s file.ui" % sys.argv[0],
sys.exit(1)
app = qt.QApplication(sys.argv)
app.connect(app, qt.SIGNAL("lastWindowClosed()"), app, qt.SLOT("quit()"))
klass = load(sys.argv[1])
w = klass()
app.setMainWidget(w)
w.show()
print "Widget: %r" % w
app.exec_loop()
In PyQt4
In PyQt4 you can just use the PyQt4.uic module, which will return a class, with PyQt4.uic.loadUiType().
PyQt Wiki