суббота, 24 апреля 2010 г.

PyQt: Генерация кода по ресурсу интерфейса

В подавляющем большинстве инструментов для создания GUI, которые мне доводилось использовать, для нормальной работы программы положено из ресурса некоторым образом сгенерировать код. Естественно, полученную рыбу надо наполнить прикладной логикой.

Веселуха начинается, когда возникает необходимость изменить ресурс. Путей при этом два: описания новых контролов ресурса добавлять в ранее сгенерированный код руками по образу и подобию существующих или понадеяться на корректную перегенерацию.


Оба пути не очень. Первый более безопасный (нет шансов потерять код прикладной логики) но медленный, трудоемкий и чреватый ошибками. На втором есть серьезная опасность потерять код, написанный руками, генератор-то о нем ничего не знает. Чтобы снизить вероятность повреждения кода прикладной логики, как правило, генераторы оборачивают свой код в специально обученные комментарии. Эти уродливые комменты бесят меня неимоверно и хожу я обычно по первому пути.

PyQt в этом отношении сильно радует. Код сгенерированного класса формы не предназначен для ручной правки, от него предлагается наследовать или агрегировать (слава великой концепции сигналов-слотов). Более того, в PyQt есть возможность динамически генерировать классы форм по ресурсу без создания промежуточного файла. Я был в восторге. Все вышеописанное позволяет написать компактную фабрику классов виджетов, требующую на входе имя файла ресурсов и классы, реализующие прикладную логику.
from PyQt4.uic import loadUiType

class CWidgetFactory(object):
    u'''
    Абстрактная фабрика классов виджетов PyQt
    
    '''

    @classmethod
    def create(cls, fileName, slotHandleClasses=None):
        u'''
        Создает класс виджета PyQt.
        
        fileName - имя файла ресурса ui
        slotHandleClasses - итератор, возвращающий базовые классы, реализующие прикладную логику виджета.
        
        Порядок разрешения методов (MRO) создаваемого класса - прикладные базовые классы,
        в порядке выдачи итератором slotHandleClasses, базовый класс виджета Qt.
        
        Такой MRO обеспечивает возможность переопределения виртуальных методов объектов Qt.
        
        '''
        uiClass, qtBaseClass = loadUiType(fileName)
        base_classes = (tuple(slotHandleClasses) or ()) + (qtBaseClass,)
        return type(str(fileName), base_classes, {u'uiClass':uiClass,
                                                  u'__init__':cls.__constructor,
                                                  u'slotHandleClasses':slotHandleClasses})

    @staticmethod
    def __constructor(self, parent=None, *args, **kwargs):
        u'''
        Конструктор класса виджета.
        Обеспечивает необходимый порядок вызовов конструкторов базовых классов
        и инициализирует интерфейс.
        
        '''
        self.__class__.__base__.__init__(self, parent)
        self.ui = self.uiClass()
        self.ui.setupUi(self)
        if self.__class__.slotHandleClasses:
            [base_cls.__init__(self, *args, **kwargs) 
             for base_cls 
             in self.__class__.slotHandleClasses]

Использовать фабрику очень просто:
EntityDialog = CWidgetFactory.create('entity.ui', [EntityDialogHandler])
dlg = EntityDialog(parent)
dlg.exec_()

Здесь, entity.ui - файл ресурса диалога, EntityDialogHandler - класс прикладной логики диалога, их может быть несколько.

Для упрощения создания главного окна и приложения PyQt есть еще один маленький класс:
from PyQt4.QtCore import QTranslator
from PyQt4.QtGui import QApplication
from wfactory import CWidgetFactory

class CommonApp(QApplication):
    u'''
    Обертка над классом приложения Qt. Автоматизирует создание главного окна приложения,
    инициализирует локализацию приложения.
    
    '''

    def __init__(self, args, ui_file, trans_file=None, handles=None, maximize=True):
        u'''
        args - список внешних аргументов, как правило, sys.argv
        ui_file - ресурс главного окна
        trans_file - файл локализации
        handles - итератор прикладных базовых классов
        maximize - признак максимизации главного окна при старте приложения.
        
        '''
        
        QApplication.__init__(self, args)
        if trans_file:
            self.translator = QTranslator(self)
            self.translator.load(trans_file)
            self.installTranslator(self.translator)
        self.main_wnd = CWidgetFactory.create(ui_file, handles)()
        self.setActiveWindow(self.main_wnd)
        if maximize:
            self.main_wnd.showMaximized()
        else:
            self.main_wnd.show()

Использовать так:
import sys
from wfactory import CommonApp
from mainwnd import MainWndHandler

if __name__ == u'__main__':
    app = CommonApp(sys.argv, u'main.ui', None, [MainWndHandler])
    app.exec_()


P.S. Разговор выше шел об оконных ресурсах формата ui. В Qt (и PyQt) есть еще один вид ресурсов (он так и называется - ресурсы :), содержащий, как правило, графику и также требующий генерации кода - qrc. Вот для этих ресурсов не предусмотрена динамическая генерация объектов языка, видимо в связи с особенностями реализации, их приходится генерировать вручную. А очень жаль.

Комментариев нет:

Отправить комментарий