ui-Datei im Resource-Datei

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

eine Weile lang habe ich es so gehandhabt, dass ich die *.ui-Dateien immer dynamisch geladen habe. Aber da py2exe mit *.ui-Dateien insofern ein Problem hat, dass es diese Dateien nicht mit kopiert beim Erstellen der *.exe-Datei, so dachte ich mir, mache ich es so wie mit den Bild-Dateien. Denn Python-Dateien werden von py2exe weitestgehend ohne Probleme mit kopiert. Ich habe die ui-Dateien in eine Resource-Datei geladen, diese Datei dann mittel pyrcc4-Befehl in eine Python-Datei umgewandelt. Alles prima.

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
import sys

from PyQt4.QtCore import Qt, QFile
from PyQt4.uic import loadUi
from PyQt4.QtGui import QDialog

from xarphus.gui import ui_rc

BASE_PATH = os.path.dirname(os.path.abspath(__file__))
#UI_PATH = os.path.join(BASE_PATH, 'gui', 'create_user.ui')
UI_PATH = QFile(":/ui_file/create_user.ui")

class CreateUser_Window(QDialog):
    def __init__(self, parent):

        QDialog.__init__(self, parent)

[...]
        UI_PATH.open(QFile.ReadOnly)
        self.ui_create_user = loadUi(UI_PATH, self)
        UI_PATH.close()
[...]
Wir sehe hier, dass die umgewandelte Resource-Datei (hier die ui_rc-Datei) importiert wird. Anschließend wird ui-Datei (create_user.ui) aus einer Resource-Datei geladen. Diese Datei wird dann lesend geöffnet, und anschließend wieder geschlossen. Es klappt alles soweit ganz gut. Nur wenn ich das Fenster ein zweites Mal öffne, dann bekomme ich die unten stehende Fehlermeldung. Das heißt, beim ersten Mal wird das Fenster ordnungsgemäß geöffnet. Ich schließe das Fenster, und versuche es gleich nochmal zu öffnen, und dann bekomme ich diese Fehlermeldung.

Die Fehlermeldung sieht wie folgt aus:
Traceback (most recent call last):
File "D:\Dan\Python\xarphus\xarphus\frm_mdi.py", line 359, in create_update_form
self.update_form = Update_Window(self)
File "D:\Dan\Python\xarphus\xarphus\frm_update.py", line 135, in __init__
self.ui_update = loadUi(UI_PATH, self)
File "C:\Python27\lib\site-packages\PyQt4\uic\__init__.py", line 238, in loadUi
return DynamicUILoader(package).loadUi(uifile, baseinstance, resource_suffix)
File "C:\Python27\lib\site-packages\PyQt4\uic\Loader\loader.py", line 71, in loadUi
return self.parse(filename, resource_suffix, basedir)
File "C:\Python27\lib\site-packages\PyQt4\uic\uiparser.py", line 984, in parse
document = parse(filename)
File "C:\Python27\lib\xml\etree\ElementTree.py", line 1182, in parse
tree.parse(source, parser)
File "C:\Python27\lib\xml\etree\ElementTree.py", line 657, in parse
self._root = parser.close()
File "C:\Python27\lib\xml\etree\ElementTree.py", line 1654, in close
self._raiseerror(v)
File "C:\Python27\lib\xml\etree\ElementTree.py", line 1506, in _raiseerror
raise err
xml.etree.ElementTree.ParseError: no element found: line 1, column 0
Kann mir jemand bitte sagen, was ich falsch mache? Was übersehe ich hier?
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich glaube eine Lösung gefunden zu haben, jedoch bin ich mir nicht sicher ob es "pythonisch" ist bzw. ob ich einfach nur Glück hatte, dass es funktioniert. Nun, ich dachte mir einfach, wieso sollte man nicht die __init__-Datei benutzen. Ich weiß, dass diese Dateien in einem Projekt in erster Linie für Python-Pakete verwendet werden, aber ich versuchte es einfach.

Nun, zunächst einmal definierte ich in der__init__-Datei eine Funktion wie diese:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from PyQt4.uic import loadUi
from PyQt4.QtCore import Qt, QFile

def ui_load_about(self):
    uiFile = QFile(":/ui_file/about.ui")
    uiFile.open(QFile.ReadOnly)
    self.ui_about = loadUi(uiFile)
    uiFile.close()
    print "STATUS [OK]  (", FILE_NAME, "): The function (ui_load_about) is called"
    return self.ui_about


In einem anderem Modul (hier about.py) ging ich dann wie folgt vor:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
import sys

from PyQt4.QtCore import Qt, QFile
from PyQt4.uic import loadUi
from PyQt4.QtGui import QDialog

import __init__ as ui_file

class About_Window(QDialog):
    def __init__(self, parent):

        QDialog.__init__(self, parent)

        self.ui_about = ui_file.ui_load_about(self)
Hier habe ich die __init__-Datei als ui_file importiert. Und in der Klasse rufe ich dann die ui_file.ui_load_about(self)-Funktion auf, und speichere den Rückgabewert in die Variable self.ui_about.

Dies führte dann dazu, dass ich dann in noch anderem Modul (hier: mdi.py) folgendes tun musste:

Code: Alles auswählen

def create_about_form(self):
    self.ui_about = About_Window(self)

    # Now when I try to show (show()-method) a window, but I get two windows.
    # The reason is: I open and load the ui files from compiled
    # qt resorce file that was define in __init__-module. 
    # There is a function that opens the resource file, reads  
    # the ui file an closes and returns the ui file back

    # That's the reason why I have commented out this method
    #self.ui_about.show()
Ich musste die show()-Methode auskommentieren, da ich sonst zwei Fenster bekam. Dies habe ich versucht auf englisch zu erklären.

Es funktioniert alles so wie ich es mir vorstelle. Die *.ui-Datei wird aus dem Kompilat (Ressource-Datei) geladen, und angezeigt. Und ich kann das Fenster immer und immer wieder öffnen, und nicht mehr nur einmal wie vorher.

Nun eine Frage an euch: Hatte ich einfach nur Glück oder kann man dies als Lösung gelten lassen? Gibt es eine bessere oder andere elegantere Lösung? Und kann mir jemand vielleicht erklären, wieso diese (vermeintliche) Lösung klappt, und die Lösung im ersten Beitrag nicht? Ich meine, der Vorgang ist genau die selbe, nur dass es diesmal in eine __init__-Datei verlegt wurde und nicht direkt in der Klasse, wie im ersten Beitrag.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: Du hast mal wieder typisch etliche Sachen gleichzeitig geändert und herumgeraten, statt das Problem Stück für Stück zu analysieren. Also, was ist der entscheidende Unterschied zwischen den beiden Versionen, wenn Du mal Dein Vodoo mit der __init__.py wegläßt?
Eine einmal gelesenes Resourcen-QFile läßt sich scheinbar nicht ein zweites mal öffnen. Auch ein Grund, warum man außer Konstanten und Definitionen nichts auf Moduleben haben sollte.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: So dachte ich es mir auch, dass die Ressource-Datei nur ein einziges Mal gelesen werden kann. Warum weiß ich leider nicht. Daher bin ich auf die __init__-Datei gekommen, die im Grunde nur ein einziges Mal eingelesen wird. Im ersten Beitrag sehen wir in Zeile 23-25, dass das Lesen der QFile-Datei auf Klassen-Ebene stattfindet, nicht auf Modulebene. Oder übersehe ich etwas? In der Klasse wird die Datei gelesen, und wieder geschlossen.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: nein, bei Deiner __init__-Version erzeugst Du jedesmal ein neues QFile.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Du hast vollkommen Recht. Danke sehr. Allerdings ist mir aufgefallen, dass bei einem Kompilat die Fenster nicht mehr mit der show()-Methode aufgerufen werden muss. Ich habe deine Anmerkung umgesetzt, dass heißt, ich habe uiFile = QFile(":/ui_file/about.ui") aus dem Modul von der __init__-Datei entnommen und anschließend auf Klassen-Ebene verlegt. Klappt alles. Aber wie auch bei der __init__-Version brauche ich beim Aufruf des Fenster keine show()-Methode, es reicht nur diese Funktion, zum Aufrufen des Fensters:

Code: Alles auswählen

def create_about_form(self):
    self.ui_about = About_Window(self)
    #self.ui_about.show()
Und auf der Klassen-Ebene des "about"-Fensters sieht so aus:

Code: Alles auswählen

class About_Window(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)

        UI_PATH = QFile(":/ui_file/about.ui")
        UI_PATH.open(QFile.ReadOnly)
        self.ui_about = loadUi(UI_PATH, self)
        UI_PATH.close()
Meine Erklärung, warum ich die show()-Methode nicht benötige, ist, dass damit zusammenhängt, dass die ui-Dateien nun kompiliert wurden, und durch den Zugriff auf das Resourcen-QFile "erspare" ich mir diesen eben genannte Methode? Bestimmt liege ich falsch.
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Wieso nicht einfach die py2exe docs lesen und die ui files mitkopieren lassen? http://www.py2exe.org/index.cgi/data_files
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@apollo13: Weil ich hier genauso verfahren möchte wie mit den den Bildern. Ich packe die ui-Dateien in die Ressourcen-Datei und kompiliere sie. So kommt später nicht das Problem auf, dass die Dateien an unterschiedlichen Orten liegen könnte.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Sophus hat geschrieben:@apollo13: Weil ich hier genauso verfahren möchte wie mit den den Bildern. Ich packe die ui-Dateien in die Ressourcen-Datei und kompiliere sie. So kommt später nicht das Problem auf, dass die Dateien an unterschiedlichen Orten liegen könnte.
Bezogen auf Dein (Schein-)Problem, dass die Bilder oder die ui-Dateien plötzlich woanders liegen könnten, ist das keine Lösung. Genauso wahrscheinlich könnten diese "Kompilate" sich verschieben. Als Paket-Verantwortlicher ist man nie vor sich selbst gefeit ;)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@jerch: Diesmal habe ich die QT-Dokumentation dazu gelesen :)

Dort steht:


The Qt resource system is a platform-independent mechanism for storing binary files in the application's executable. This is useful if your application always needs a certain set of files (icons, translation files, etc.) and you don't want to run the risk of losing the files.


Neben diese Plattformunabhängigkeit der gespeicherten binären Dateien wird auch das Verlust-Risiko der Dateien angesprochen :-)
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Sophus hat geschrieben:Neben diese Plattformunabhängigkeit der gespeicherten binären Dateien wird auch das Verlust-Risiko der Dateien angesprochen :-)
Und die Binärdateien kann man nicht verlieren? LOL :D
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@apollo13: Nein nein. Ich hätte nur gern alles beisammen und nicht so verstreut. Es spricht ja nichts gegen einer Ressourcen-Datei und diese wird ja nicht nur für Bilder verwendet :-) Ich denke, es ist eher eine Frage des Geschmacks.
Antworten