QUiLoader/custom methods

187 views
Skip to first unread message

Wolfgang P.

unread,
Nov 17, 2011, 3:56:49 PM11/17/11
to lqt-bindings
I have the following problem:
When loading a widget from a *.ui file (with the QUiLoader class), I
can't add any methods to that widgets children (with __addmethod).
As far as I can tell, this prevents me from properly using signals/
slots with all the objects fro the *.ui file...

I have admittedly no clue about how lqt works internally/if there is
some way to circumvent this problem/if its a mistake on my part, but I
read all documentation and tried to create a minimal testcase (needs a
test.ui - file with one QPushButton named pushButton):

require 'qtcore'
require 'qtgui'
require 'qtuitools'

local function loadUiFile(file)
local f = QFile.new_local(file)
f:open({"ReadOnly"})
uil = QUiLoader.new_local()
local w = uil:load(f)
f:close()
return w
end

app = QApplication.new(select('#',...) + 1, {'lua', ...})

--just needs to contain one pushButton named 'pushButton'
local w = loadUiFile "test.ui"

local button = w:findChild("pushButton")
local button2 = QPushButton.new_local()

button.mySlot = function(self)
print("slot, from button 1")
end
button:__addmethod("mySlot()", button.mySlot)
button2.mySlot = function(self)
print("slot, from button 2")
end
button2:__addmethod("mySlot()", button2.mySlot)

assert(#button:__methods() == #button2:__methods())

w:show()

app.exec()

w:delete()

Michal Kottman

unread,
Nov 18, 2011, 5:04:10 AM11/18/11
to lqt-bi...@googlegroups.com
On 17 November 2011 21:56, Wolfgang P. <wolfga...@gmail.com> wrote:
> I have the following problem:
> When loading a widget from a *.ui file (with the QUiLoader class), I
> can't add any methods to that widgets children (with __addmethod).
> As far as I can tell, this prevents me from properly using signals/
> slots with all the objects fro the *.ui file...

This is known problem, and is caused by how Lqt handles custom slots.

> I have admittedly no clue about how lqt works internally

In order for this to work, Lqt has to create subclasses for every
QObject-derived class. When you call 'QWidget.new()', Lqt actually
creates this new subclass.

The UiLoader however creates "raw" objects, and therefore Lqt is
unable to add custom slots to them.

> if its a mistake on my part

No it isn't :)

> if there is some way to circumvent this problem

One way to overcome this problem is to create an object from Lqt
(which will have custom slots ability), and use it to handle all the
signals that you need, i.e.:

local w = loadUiFile "test.ui"
local button = w:findChild("pushButton")

local handler = QObject.new()

handler:__addmethod('handleButton()', function() print("Handled!") end)
handler:connect(button, '2pressed()', '1handleButton()')

Wolfgang P.

unread,
Nov 18, 2011, 9:29:16 PM11/18/11
to lqt-bindings
Your explanation was very helpful, thanks!
I'm not quite sure yet how this limitation affects me, but I don't
think it's a big deal (as far as i can tell, upvalues make the signal/
slot system a whole lot more powerful anyway).

To fix this, I assume one would have to reimplement at least part of
the UI- loader in Lua to generate all objects properly?
I just have no idea how much time/knowledge it would take to make this
work properly; I'd definitely give it a look if I thought I could do
it.

Michal Kottman

unread,
Nov 19, 2011, 3:42:07 PM11/19/11
to lqt-bi...@googlegroups.com

You would have to study the actual source of QUiLoader, because it
involves some parsing and setting up. I have never seen it myself, so
I am not sure how hard it would be to implement it. However, I feel
that it should be possible to do in plain Lua (once QtCore, QtGui and
QtXml modules are loaded).

forester

unread,
Nov 29, 2011, 5:45:13 PM11/29/11
to lqt-bindings
Having hit the same inconvenience, I've extended the UiLoader to
generate all the widgets and layouts directly from lua.
Not all the widgets are handled but can be added easily.
The createLayout was copied from the cpp factory implementation.
Use findObjectByName to retrieve widgets. QWidget:findChild might also
work but I didn't try it
Can't attach files so here it goes:

----- copy in uiLoader.lua -----

require 'qtcore'
require 'qtgui'
require 'qtuitools'

module('uiLoader', package.seeall)

local mLoader = QUiLoader.new_local()
local mObjectsByName = {}

mLoader.createWidget = function (loader, _className, parent, name)
local className = _className:toUtf8()

local widget
if className == 'QMainWindow' then
widget = QMainWindow.new(parent)
elseif className == 'QWidget' then
widget = QWidget.new(parent)
elseif className == 'QMenuBar' then
widget = QMenuBar.new(parent)
elseif className == 'QMenu' then
widget = QMenu.new(parent)
elseif className == 'QStatusBar' then
widget = QStatusBar.new(parent)
elseif className == 'QDockWidget' then
widget = QDockWidget.new(parent)
elseif className == 'QGroupBox' then
widget = QGroupBox.new(parent)
elseif className == 'QToolButton' then
widget = QToolButton.new(parent)
elseif className == 'QTreeView' then
widget = QTreeView.new(parent)
elseif className == 'QListView' then
widget = QListView.new(parent)
elseif className == 'QListWidget' then
widget = QListWidget.new(parent)
elseif className == 'QTabWidget' then
widget = QTabWidget.new(parent)
else
error('Cannot create object: '..tostring(className))
widget = QWidget.new(parent)
end

widget.__isWidget = true
widget:setObjectName(name)

local n = name:toUtf8()
mObjectsByName[n] = widget
return widget
end

mLoader.createLayout = function (loader, _className, parent, name)
local parentWidget = parent.__isWidget and parent or nil
local parentLayout = parent.__isLayout and parent or nil

local function create(class)
return parentWidget and class.new(parentWidget) or class.new()
end

local layout

local className = _className:toUtf8()
if className == 'QVBoxLayout' then
layout = create(QVBoxLayout)
elseif className == 'QHBoxLayout' then
layout = create(QHBoxLayout)
else
print('Cannot create layout: '..tostring(className))
layout = create(QLayout)
end

layout.__isLayout = true
layout:setObjectName(name)

local n = name:toUtf8()
mObjectsByName[n] = layout
return layout
end

function load(fileName)
local file = QFile.new_local(fileName)
file:open({'ReadOnly'})

return mLoader:load(file)
end

function findObjectByName(name)
return mObjectsByName[name]
end

---------


On Nov 19, 9:42 pm, Michal Kottman <k0mpju...@gmail.com> wrote:

Michal Kottman

unread,
Nov 29, 2011, 7:37:38 PM11/29/11
to lqt-bi...@googlegroups.com
On 29 November 2011 23:45, forester <catalin...@gmail.com> wrote:
> Having hit the same inconvenience, I've extended the UiLoader to
> generate all the widgets and layouts directly from lua.

Neat! I didn't know this was so easy :)

> Not all the widgets are handled but can be added easily.
> The createLayout was copied from the cpp factory implementation.
> Use findObjectByName to retrieve widgets. QWidget:findChild might also

> work but I didn't try it.

We could simplify the implementation by using some Lua magic :)

> mLoader.createWidget = function (loader, _className, parent, name)
>        local className = _className:toUtf8()
>
>        local widget
>        if className == 'QMainWindow' then
>                widget = QMainWindow.new(parent)

> ....


>        else
>                error('Cannot create object: '..tostring(className))
>                widget = QWidget.new(parent)
>        end

This can be rewritten as:

mLoader.createWidget = function (loader, _className, parent, name)
local className = _className:toUtf8()

local class = _G[_className]
local widget
if class then
widget = class.new(parent)
else
error('Cannot create object: '..tostring(_className))
widget = QWidget.new(parent)
end

The same can be done in createLayout.

forester

unread,
Dec 3, 2011, 6:50:01 AM12/3/11
to lqt-bindings
You're right, it's way simpler. I'm still surprised by how expressive
Lua can be.
Here is the new version:

----------------


require 'qtcore'
require 'qtgui'
require 'qtuitools'

module('uiLoader', package.seeall)

local mLoader = QUiLoader.new_local()
local mObjectsByName = {}

mLoader.createWidget = function (loader, _className, parent, name)


local className = _className:toUtf8()

local widget
local class = _G[className]


if class then
widget = class.new(parent)
else

error('Cannot create object: '..tostring(className))
end

widget.__isWidget = true
widget:setObjectName(name)

local n = name:toUtf8()
mObjectsByName[n] = widget
return widget
end

mLoader.createLayout = function (loader, _className, parent, name)
local parentWidget = parent.__isWidget and parent or nil
local parentLayout = parent.__isLayout and parent or nil

local function create(class)
return parentWidget and class.new(parentWidget) or class.new()
end

local layout

local className = _className:toUtf8()

local class = _G[className]
if class then
layout = create(class)
else
error('Cannot create layout: '..tostring(className))
end

layout.__isLayout = true
layout:setObjectName(name)

local n = name:toUtf8()
mObjectsByName[n] = layout
return layout
end

function load(fileName)
local file = QFile.new_local(fileName)
file:open({'ReadOnly'})

return mLoader:load(file)
end

function findObjectByName(name)
return mObjectsByName[name]
end

-----------------

On Nov 30, 1:37 am, Michal Kottman <k0mpju...@gmail.com> wrote:

Reply all
Reply to author
Forward
0 new messages