Skip to main content

Ralsina.Me — Roberto Alsina's website

Indeed screw all gui builders... for java!

Read­ing DZone I ran in­to an in­ter­est­ing post ti­tled Screw all GUI builders which ad­vo­cates drop­ping all GUI builders and in­stead cod­ing your UI by hand.

I al­ways ad­vo­cate us­ing Qt De­sign­er in­stead of cod­ing by hand, so I want­ed to see, even it's talk­ing about Java, I thought, "why don't I feel that way"?

My con­clu­sion? I don't see it be­cause in PyQt we are just lucky be­cause our tools don't suck quite as much.

It gives sev­er­al ar­gu­ments:

  • You don't know how ex­ac­t­­ly the gen­er­at­ed code work­s. You don't need to. You start not to care and GUI ap­­pli­­ca­­tion de­vel­op­­ment be­­comes a process of draw­ing and adding sim­­ple event han­dlers here and there.

To that, my re­ac­tion was yes, in­deed I don't care, as long as it work­s, which it has 99.99% of the time. When it did­n't, I did un­der­stand the gen­er­at­ed code, though.

The rea­son for this will be more ob­vi­ous once you see the code in both cas­es, I think.

  • Most GUI builders force you to use sin­­gle class for sin­­gle win­­dow, so gen­er­at­ed class­es tend to have thou­sands of lines of code.

Hm­m­m... I re­al­ly don't know what this means in con­tex­t, but that's just my ja­va ig­no­rance. OTO­H, yes, one class per win­dow, but al­most nev­er thou­sands of lines of code.

For ex­am­ple, the UI for uRSSus main win­dow is quite com­plex, and it is on­ly 530 lines of code. Here's how it look­s, so you see it's not a triv­ial win­dow:

urssus23 * Most GUI builders don't want you to modify the generated code. And if you do, they either break or rewrite your code.

In­deed De­sign­er's code is not meant to be mod­i­fied. That's what in­her­i­tance is there for. Over­load what­ev­er you want changed. Maybe this is eas­i­er in PyQt be­cause Python is more dy­nam­ic than Java? Not sure.

  • GUI builders force you to use an IDE, most­­ly one you start­ed cod­ing with. So if you start with Net­Bean­s, you most like­­ly be forced to stay with it for the whole pro­jec­t.

Just not true for De­sign­er. I can see how that would suck, though.

  • The gen­er­at­ed code is far from be­ing op­ti­­mal. It's not re­­size-friend­­ly, not dy­­nam­ic enough, it has many hard-­­cod­ed val­ues, refac­­tor­ing is most like­­ly im­­pos­si­ble, be­­cause builder would not al­low that.

De­sign­er does gen­er­ate re­size-friend­ly di­alogs if you use it cor­rect­ly. The hard-­cod­ed val­ues are run­time-ed­itable, refac­tor­ing is pret­ty sim­ple (take what­ev­er you wan­t, cre­ate a wid­get with it?)

And since I want­ed to com­pare ap­ples to ap­ples...

Here is his di­alog, done via PyQt and De­sign­er:

hawkscope1

No, I did­n't both­er in­sert­ing text in the di­a­log so it loks the same ;-)

The "close" but­ton clos­es the win­dow, the URL opens in your sys­tem brows­er.

The text wid­get ac­tu­al­ly sup­ports a sub­set of HTM­L, so there is a valid HTML doc­u­ment there, in­stead of just plain tex­t.

Al­so, an­oth­er thing is ... De­sign­er's (or rather pyuic's) gen­er­at­ed code is straightor­ward stuff.

Here's the code, which I think is rough­ly equiv­a­lent to his, on­ly it's just 123 LOC, in­stead of 286 (his gen­er­at­ed ver­sion) or 279 (his hand-­made ver­sion). And I did­n't delete the com­ments.

If I were do­ing this for re­al, all wid­gets would have de­scrip­tive names in­stead of Push­But­ton1 and what­ev­er.

Al­so, it's i18n-ready, un­like the Ja­va ver­sion­s, un­less I missed some­thing.

You can al­so get hawkscope.ui from this site to play with, it's done with De­sign­er from Qt 4.4.

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'hawkscope.ui'
#
# Created: Fri Feb 13 22:39:47 2009
#      by: PyQt4 UI code generator 4.4.4
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(444, 357)
        self.verticalLayout_2 = QtGui.QVBoxLayout(Dialog)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.horizontalLayout_2 = QtGui.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.label_2 = QtGui.QLabel(Dialog)
        self.label_2.setPixmap(QtGui.QPixmap("hawkscope.png"))
        self.label_2.setAlignment(QtCore.Qt.AlignCenter)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout_2.addWidget(self.label_2)
        self.verticalLayout = QtGui.QVBoxLayout()
        self.verticalLayout.setObjectName("verticalLayout")
        self.label_3 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_3.setFont(font)
        self.label_3.setObjectName("label_3")
        self.verticalLayout.addWidget(self.label_3)
        self.label_4 = QtGui.QLabel(Dialog)
        self.label_4.setObjectName("label_4")
        self.verticalLayout.addWidget(self.label_4)
        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName("gridLayout")
        self.label_5 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_5.setFont(font)
        self.label_5.setObjectName("label_5")
        self.gridLayout.addWidget(self.label_5, 0, 0, 1, 1)
        self.label_10 = QtGui.QLabel(Dialog)
        self.label_10.setObjectName("label_10")
        self.gridLayout.addWidget(self.label_10, 0, 1, 1, 1)
        self.label_9 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_9.setFont(font)
        self.label_9.setObjectName("label_9")
        self.gridLayout.addWidget(self.label_9, 1, 0, 1, 1)
        self.label_8 = QtGui.QLabel(Dialog)
        self.label_8.setObjectName("label_8")
        self.gridLayout.addWidget(self.label_8, 1, 1, 1, 1)
        self.label_7 = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label_7.setFont(font)
        self.label_7.setObjectName("label_7")
        self.gridLayout.addWidget(self.label_7, 2, 0, 1, 1)
        self.label_6 = QtGui.QLabel(Dialog)
        self.label_6.setOpenExternalLinks(True)
        self.label_6.setObjectName("label_6")
        self.gridLayout.addWidget(self.label_6, 2, 1, 1, 1)
        self.verticalLayout.addLayout(self.gridLayout)
        self.horizontalLayout_2.addLayout(self.verticalLayout)
        self.verticalLayout_2.addLayout(self.horizontalLayout_2)
        self.label = QtGui.QLabel(Dialog)
        font = QtGui.QFont()
        font.setWeight(75)
        font.setBold(True)
        self.label.setFont(font)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        self.textBrowser = QtGui.QTextBrowser(Dialog)
        self.textBrowser.setObjectName("textBrowser")
        self.verticalLayout_2.addWidget(self.textBrowser)
        self.horizontalLayout = QtGui.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.pushButton = QtGui.QPushButton(Dialog)
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout.addWidget(self.pushButton)
        self.pushButton_2 = QtGui.QPushButton(Dialog)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        self.verticalLayout_2.addLayout(self.horizontalLayout)

        self.retranslateUi(Dialog)
        QtCore.QObject.connect(self.pushButton_2, QtCore.SIGNAL("clicked()"), Dialog.accept)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.label_3.setText(QtGui.QApplication.translate("Dialog", "Hawkscope", None, QtGui.QApplication.UnicodeUTF8))
        self.label_4.setText(QtGui.QApplication.translate("Dialog", "Access anything with single click!", None, QtGui.QApplication.UnicodeUTF8))
        self.label_5.setText(QtGui.QApplication.translate("Dialog", "Version:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_10.setText(QtGui.QApplication.translate("Dialog", "0.4.1", None, QtGui.QApplication.UnicodeUTF8))
        self.label_9.setText(QtGui.QApplication.translate("Dialog", "Released:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_8.setText(QtGui.QApplication.translate("Dialog", "2009-02-06", None, QtGui.QApplication.UnicodeUTF8))
        self.label_7.setText(QtGui.QApplication.translate("Dialog", "Homepage:", None, QtGui.QApplication.UnicodeUTF8))
        self.label_6.setText(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><a href=\"http://hawkscope.googlecode.com\"><span style=\" text-decoration: underline; color:#3c7dbe;\">http://hawkscope.googlecode.com</span></a></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.label.setText(QtGui.QApplication.translate("Dialog", "Environment", None, QtGui.QApplication.UnicodeUTF8))
        self.textBrowser.setHtml(QtGui.QApplication.translate("Dialog", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:\'Droid Sans\'; font-size:8pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Text goes here</p>\n"
"<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"></p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setText(QtGui.QApplication.translate("Dialog", "C&opy To Clipboard", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton.setShortcut(QtGui.QApplication.translate("Dialog", "Alt+O", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButton_2.setText(QtGui.QApplication.translate("Dialog", "&Close", None, QtGui.QApplication.UnicodeUTF8))
Andy Pardue / 2009-02-15 03:26:

I have been writing cross platform apps with PyQt for over 5 years. I use designer for the main form and widgets. In my book it is the fastest way to write Windows/OS X / Linux apps period. The generated files stay out of the way I have massively re factored projects interfaces effortlessly. I love PyQt I work in Eric3/4 IDE I am totally spoiled.

I deploy my windows versions with py2exe. I should be able to get your app to work with it I will let you know.

Andy

Roberto Alsina / 2009-02-15 08:03:

Great!


Contents © 2000-2024 Roberto Alsina