Bild wird beim Resize des Hauptfensters nicht mitskaliert

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
antimatter
User
Beiträge: 13
Registriert: Freitag 8. Oktober 2010, 16:44

Hallo,
auf meiner GUI wird ein Bild als Pixmap in einem Label angezeigt. Wenn ich die Größe des Fensters mit der Maus weiter aufziehe, bleibt das Bild jedoch unverändert und skaliert nicht entsprechend mit. Es wird höchstens innerhalb des Labels zentriert, je nach Art der gewählten Ausrichtung.
Ich habe dabei die relevanten Codefragmente aus dem PyQt4-Beispiel "Screenshot" herauskopiert. Dort funktioniert das super. Die resizeEvent() Funktion wird mit dem entsprechenden Code überschrieben.
Ich versuche mich aber bereits seit 3 Stunden an meiner eigenen GUI daran, aber ich finde den Fehler nicht. Den einzigen Hinweis, den ich soweit entdeckt habe ist, dass die resizeEvent() bei mir nicht aufgerufen wird. Ersichtlich an dem fehlenden "resized" auf der Konsole.

Kann mir jemand sagen, was ich falsch mache? QMainWindow erbt doch von QWidget die resizeEvent(), oder nicht?

Viele Grüße
antimatter

Code: Alles auswählen

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import uic

class MainWindow(QMainWindow):
    
    def __init__(self):
        super(MainWindow, self).__init__()
        
        self.ui = uic.loadUi('test.ui')
        self.ui.show()
        self.openImage()
        
    def openImage(self):
        self.originalPixmap = None
        self.originalPixmap = QPixmap.fromImage(QImage(QString("2002-04-06-45.tif")))
        self.updateLabel()
    
    def resizeEvent(self, event):
        
        print "resized"
        
        scaledSize = self.originalPixmap.size()
        scaledSize.scale(self.ui.lblImage.size(), Qt.KeepAspectRatio)
        if not self.ui.lblImage.pixmap() or scaledSize != self.ui.lblImage.pixmap().size():
            self.updateLabel()
        
    def updateLabel(self):
        self.ui.lblImage.setPixmap(self.originalPixmap.scaled(
            self.ui.lblImage.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainwindow = MainWindow()
    sys.exit(app.exec_())
test.ui

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>256</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QLabel" name="lblImage">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>TextLabel</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>344</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Hi
Bin mir nicht sicher ob es so gedacht ist, aber so tut es:

Du musst jetzt halt dein Label in MyLabel umwandeln.

Code: Alles auswählen

class MyLabel(QLabel):

    def __init__(self, parent=None):
        super(MyLabel, self).__init__(parent)
        self.openImage()

    def openImage(self):
        self.originalPixmap = QPixmap.fromImage(QImage(QString("2002-04-06-45.tif")))
        self.updateLabel()
        
    def resizeEvent(self, event):
        print "resized Label"    
        scaledSize = self.originalPixmap.size()
        scaledSize.scale(self.size(), Qt.KeepAspectRatio)
        scaledSize.scale(self.size(), Qt.KeepAspectRatio)
        if not self.pixmap() or scaledSize != self.pixmap().size():
            self.updateLabel()
       
    def updateLabel(self):
        self.setPixmap(self.originalPixmap.scaled(
            self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
Warum kann ich dir aber gerade auch nicht sagen ...

Gruß
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Ups und die Main

Code: Alles auswählen

class MainWindow(QMainWindow):
   
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.ui = uic.loadUi('test.ui')
        self.ui.show()
            

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainwindow = MainWindow()
    sys.exit(app.exec_())
und das ui-File.

Ich hab das Label im Designer in MyLabel umgebaut (Platzhalter für Benutzerdefinierte Klasse)

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>256</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="0">
     <widget class="MyLabel" name="lblImage">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
        <horstretch>0</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>TextLabel</string>
      </property>
      <property name="alignment">
       <set>Qt::AlignCenter</set>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>344</width>
     <height>22</height>
    </rect>
   </property>
  </widget>
 </widget>
 <customwidgets>
  <customwidget>
   <class>MyLabel</class>
   <extends>QLabel</extends>
   <header>test</header>
  </customwidget>
 </customwidgets>
 <resources/>
 <connections/>
</ui>
antimatter
User
Beiträge: 13
Registriert: Freitag 8. Oktober 2010, 16:44

Hm... du hast die meisten Funktionen in eine eigene Klasse ausgelagert, die du von QLabel ableitest.

Kannst du mir ein wenig was darüber erzählen (soweit möglich), wie du zu diesem Entschluss gekommen bist, so dass ich das halbwegs nachvollziehen kann? Ich beschäftige mich nämlich erst seit letztem Wochenende mit Qt ;-)

Viele Grüße
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Hmm nicht wirklich.
Das löst jetzt zwar dein Problem aber wie Du an das resizeEvent des QMainWindos kommst wusste/weis ich auch nicht.
Ich dachte ich schau mal ob ich an das QLabel-resizeEvent kommte und das hat dann funktioniert.
Aber wie gesagt ich hab momentan auch keinen Plan warum dein MainWindow.resizeEvent so nicht aufgerufen wird.

Gruß
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Wissen tu ichs noch immer nicht aber gefunden hab ich das hier.

Bisschen runter scrollen da ist das Bsp. in PyQt ...
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Jetzet ...

Ruf mal das mainwindow.show() auf, statt
self.ui.show()

Dann kommt das ResizeEvent an ...
ichisich
User
Beiträge: 134
Registriert: Freitag 1. Januar 2010, 11:52

Dein Problem ist das Du zwei QMainWindows hast. Einmal das im Designer gebaute und dann die Klasse die Du MainWindow genannt hast.
Ich habs jetzt mal so gelöst das ich dein Label usw nicht als QMainWindow sonder nur in ein QWidget gehängt habe und es dann im MainWindow als "centralWidget" einbinde. Die MenuBar ist auch raus geflogen kannst ja dann von hand im MainWidow einbinden.

Gruß

Code: Alles auswählen

import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from PyQt4 import uic

class MainWindow(QMainWindow):
   
 
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = uic.loadUi('test.ui')
        self.setCentralWidget(self.ui)
        self.openImage()
       
    def openImage(self):
        self.originalPixmap = None
        self.originalPixmap = QPixmap.fromImage(QImage(QString("2002-04-06-45.tif")))
        self.updateLabel()
   
    def resizeEvent(self, event):
       
        print "resized"
       
        scaledSize = self.originalPixmap.size()
        scaledSize.scale(self.ui.lblImage.size(), Qt.KeepAspectRatio)
        if not self.ui.lblImage.pixmap() or scaledSize != self.ui.lblImage.pixmap().size():
            self.updateLabel()
       
    def updateLabel(self):
        self.ui.lblImage.setPixmap(self.originalPixmap.scaled(
            self.ui.lblImage.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
       
if __name__ == '__main__':
    app = QApplication(sys.argv)
    m = MainWindow()
    print type(m), m.__dict__
    m.show()
    sys.exit(app.exec_())
und das UI-File

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>centralWidget</class>
 <widget class="QWidget" name="centralWidget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>344</width>
    <height>256</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <layout class="QGridLayout" name="gridLayout">
   <item row="0" column="0">
    <widget class="QLabel" name="lblImage">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <property name="text">
      <string>TextLabel</string>
     </property>
     <property name="alignment">
      <set>Qt::AlignCenter</set>
     </property>
    </widget>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>
lunar

Man kann das Problem auch lösen, ohne auf den graphischen Entwurf des Hauptfensters zu verzichten.

"uic.loadUi()" akzeptiert ein zweites Argument, welches dasjenige Objekt angibt, in dem die Oberfläche erstellt werden soll. Ist dieses Argument nicht gegeben, wird ein neues Objekt des passenden Typs erzeugt. Eben deswegen erzeugt der Quelltext des OP zwei Hauptfenster. Die Lösung ist also, "uic.loadUi()" mit dem zweiten Argument aufzurufen:

Code: Alles auswählen

self.loadUi('test.ui', self)
self.show()
Die sorgfältige Lektüre der Dokumentation hätte im Übrigen auch zu dieser Einsicht geführt.

Der ".show()"-Aufruf gehört allerdings eigentlich nicht in den Konstruktor, sondern in den Einstiegscode (nach der Erzeugung des "MainWindow"-Objekts). "QString" ist in Python-Quelltext vollkommen überflüssig, es reicht "QImage('2002-04-06-45.tif')". Die ungarische Notation ist unschön, statt "lblImage" wäre etwas wie "imageViewer" schöner.

Auch ist der Quelltext nicht gut strukturiert. Möchte man nur die Größe des Labels anpassen, dann ist es sinnvoller, eine eigene, von QLabel abgeleitete Klasse mit entsprechender Ereignisbehandlung zu erstellen. Es mag übrigens auch effizienter sein, das Bild selbst zu zeichnen, und die Skalierung erst beim Zeichnen vorzunehmen.

Nachtrag: Ich wäre euch beiden dankbar, wenn ihr solchen Quelltext in Zukunft in ein Pastebin (e.g. http://paste.pocoo.org) auslagern würdet. Die langen Quelltext-Blöcke stören den Lesefluss meines Erachtens doch sehr, zumal der Großteil (e.g. die UI-Dateien) in diesem Fall für das eigentliche Problem vollkommen ohne Belang waren. Außerdem lässt sich der Quelltext in einem Pastebin zum Testen direkt herunterladen, während man hier gezeigten Quelltext erst mühsam per Hand in den Texteditor kopieren und anschließend speichern muss.
antimatter
User
Beiträge: 13
Registriert: Freitag 8. Oktober 2010, 16:44

Hi lunar,
herzlichen Dank für den Hinweis. Es funktioniert!
Und jetzt wo du es sagst... ich hab bereits was über Basisklassen in Summerfields Buch "Rapid GUI Programming..." gelesen. Aber das wäre mir jetzt wohl nicht mehr eingefallen. Typischer Fall von Wald und Bäumen. :-)
Und ja, Dokumentation liegt neben mir auf dem Schreibtisch und hab ich auch ständig im Browser offen. Ohne Doku wär ich soweit gar nicht gekommen ;-)

Was die Schönheit von Variablennamen angeht, lässt sich wohl drüber streiten. Ich bevorzuge da eher die Zweckmäßigkeit, denn bei einem Präfix von "lbl..." oder "btn..." kann ich im Code auf den ersten Blick erkennen, was für ein Widget das ist.

Was die Codeblöcke angeht, hatte ich gehofft, diese werden in der Höhe beschränkt und mit Scrollbars ausgestattet. War wohl nicht so. Aber ich merke es mir und verwende in Zukunft solche pastbin's, ist kein Problem.

Ich danke auch für die Tipps bezüglich Qualität und Struktur des Codes. Wie gesagt, ich bin noch Qt-Anfänger und befinde mich noch im Prozess des Lernens. ;-)
Auch ist der Quelltext nicht gut strukturiert. Möchte man nur die Größe des Labels anpassen, dann ist es sinnvoller, eine eigene, von QLabel abgeleitete Klasse mit entsprechender Ereignisbehandlung zu erstellen. Es mag übrigens auch effizienter sein, das Bild selbst zu zeichnen, und die Skalierung erst beim Zeichnen vorzunehmen.
Kannst du mir das genauer erklären?
Mein Label soll ja zusammen mit dem MainWindow vergrößert werden. Die enthaltene Pixmap muss ja dann dementsprechend mitvergrößert werden. Das lässt sich ja nicht über den Designer festlegen. Inwiefern ist da eine eigene QLabel-Klasse hilfreich und wie binde ich die dann in das ui-File mit ein?

Wie zeichne ich ein Bild "selbst" ?

Viele Grüße
antimatter
lunar

Bei einer Unterklasse von QLabel findet die Skalierung dann genau in dem Steuerelement statt, in dem sie benötigt wird. So lässt sich eine skalierende Bildanzeige an mehreren Stellen wiederverwenden, außerdem ist der Quelltext besser verständlich, da die Verantwortlichkeiten dann klar getrennt und strukturiert sind. Im Prinzip musst Du einfach Deine ".resizeEvent()"-Implementierung aus "QMainWindow" in eine entsprechende "QLabel"-Unterklasse verschieben und geringfügig anpassen.

Im Designer kannst Du diese Klasse dann verwenden, indem Du ein QLabel als Platzhalter für eine „benutzerdefinierte Klasse“ festlegst. Alternativ kannst Du das Steuerelement auch im Quelltext erzeugen, schließlich ist der Aufruf von ".setCentralWidget()" jetzt keine übermäßig komplizierte Angelegenheit.

Um das Bild dagegen selbst zu zeichnen, musst Du in einer Unterklasse von "QWidget" ".paintEvent()" neu implementieren, ein QPainter-Objekt erzeugen, dieses entsprechend transformieren, und anschließend die Pixmap zeichnen (Beispiel). Für eine einfache Bildanzeige ist das sicherlich nicht nötig, werden allerdings viele größere und komplexere Bilder relativ oft hin- und herskaliert, ist dieser Ansatz effizienter.

Die Kopie und Manipulation (inklusive der Skalierung) von QPixmap-Objekten ist zumindest unter Linux verhältnismäßig aufwendig. Bei manuellen Zeichnen wird die Kopie und die anschließende Manipulation vermieden, indem die Skalierung dem Grafiksystem überlassen wird und erst direkt bei der Anzeige geschieht. Zudem kann man so auch von OpenGL-basierter Hardware-Beschleunigung profitieren, indem man "QWidget" durch "QGlWidget" ersetzt.
Antworten