pyqt5 graphicsView

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

Hallo,
Anfänger hat mal wieder fragen und kommt nicht weiter, im Tkinter Forum habe schon paar mal fragen gestellt da wurde mir immer geholfen (Vielen Dank dafür), da ich mir jetzt pyqt5 beibringen möchte, bin ich an meine grenzen gestoßen, ich habe mir wirklich viele Beispiele angeschaut und probiert aber es klappt bei meinem vorhaben nicht so wie ich möchte, wahrscheinlich liegt es am Verständnis zu wenig davon. Ich habe auf git code gefunden für slide show, den code würde ich gerne in meiner app unten in einem bestimmten widget anzeigen d.h ich starte meine app und die slide show fängt an in unterem teil meiner app, oben habe ich dann was anderes, ich scheitere an dem den code den ich gefunden habe in eine funktion zu packen und diese als thread aufzurufen viellleicht könnt ihr mir dabei helfen, die bilder sollen an die Größe des widget oder des graphicsView angepasst werden. 1 Bild als label habe ich mal zum test hinbekommen aber da passt auch irgendwie die auflösung nicht. Ich werde es weiter hin probieren vielleich komme ich selber drauf aber im moment bin ich irgend wie Blind und sehe nicht wo mein fehler liegt.

Vielen Dank im Voraus
Gruß ganja



hier der code schnipsel

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
from PyQt5.uic import loadUi

from os import listdir
from os.path import isfile, join
import os
import sys
import random
import time
import threading

# oberer teil meiner app
class StopWatch(Qt.QMainWindow):
	def __init__(self, parent=None):
		super().__init__(parent
               self.ui = loadUi("gui.ui", self)
       
       #verschiedene funktionen

       def slide(self):
       # hier sollte class Slideshow sein aber wie???


#gefundener code den ich in funktion packen möchte
class Slideshow():
	picture_types = ('.jpg', '.png', '.JPG', '.PNG', '.jpeg', '.JPEG')
	def __init__(self,sleep):
		super(Slideshow, self).__init__()
		print("hallo")
		self.scene = QtWidgets.QGraphicsScene()
		self.scene.setBackgroundBrush(QtGui.QBrush(Qt.black, Qt.SolidPattern))
		self.graphicsView = QtWidgets.QGraphicsView(self.scene)
		self.setCentralWidget(self.graphics_view)
		
		#self.setWindowTitle('Slideshow')
		#self.resize(1137, 885)
		#self.setWindowFlags(Qt.FramelessWindowHint)
		#self.setWindowState(Qt.WindowFullScreen)
		#self.show()

		#QtWidgets.QShortcut(QtGui.QKeySequence("SPACE"), self,self.nextPicture)

		self.path = os.getcwd()
		self.files = Slideshow.get_all_pictures(self.path)
		self._current_picture = None

		self.lock = threading.Lock()

		self._stopThread = False
		self.thread = threading.Thread(target=slideTime, args=(self, sleep))
		self.thread.daemon = True
		self.thread.start()

	def nextPicture(self):
		self.lock.acquire()
		self.files = Slideshow.get_all_pictures(self.path)
		if self.files:
			upper = len(self.files) - 1
			r = random.randint(0, upper)
			while self.files[r] == self._current_picture:
				r = random.randint(0, upper)
			self._change_picture(self.files[r])
		self.lock.release()

	def _change_picture(self, file):
		self._current_picture = file
		pixmap = QtGui.QPixmap(os.path.join(os.getcwd(), file))
		item = QtWidgets.QGraphicsPixmapItem(pixmap)
		item.setTransformationMode(Qt.SmoothTransformation)  # AA for transfoming
		self.scene.clear()
		self.scene.setSceneRect(0, 0, pixmap.width(), pixmap.height())
		self.scene.addItem(item)
		self.graphics_view.fitInView(item, Qt.KeepAspectRatio)
		print("Changed picture to " + str(file))
		self.graphicsView.mapToScene(self.graphicsView.viewport().rect().center())

	def stopThread(self):
		return self._stopThread

	@staticmethod
	def get_all_pictures(path):
		print("Generating file list...")
		list = [f for f in listdir(path) if isfile(join(path, f))
				and os.path.splitext(f)[1] in Slideshow.picture_types]
		print("Number of files: " + str(len(list)))
		return list

	def closeEvent(self, event):
		print("Stopping Thread...")
		self._stopThread = True
		event.accept()  # let the window close


def slideTime(slide, delay):
	print("Starting thread...")

	while not slide.stopThread():
		slide.nextPicture()
		time.sleep(delay)

	print("Thread stopped...")

# wird auskommentiert soll im meins alles sein da ich meine app aufrufen möchte
#def main():
#	app = QtWidgets.QApplication(sys.argv)
#	s = Slideshow(int(sys.argv[1]) if len(sys.argv) == 2 else 5)
#	i = app.exec_()
#	sys.exit(i)

#if __name__ == '__main__':
#	main()


# meins 
def main():
	app = Qt.QApplication(sys.argv)
	watch = StopWatch()
	watch.show()
	#watch.showFullScreen()
	app.exec_()

if __name__ == '__main__':
	main()

# meine gui im designer erstelllt hier sollten dann die bilder angezeigt werden
<item>
     <widget class="QWidget" name="widget_6" native="true">
      <property name="minimumSize">
       <size>
        <width>0</width>
        <height>0</height>
       </size>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout_5">
       <item>
        <widget class="QGraphicsView" name="graphicsView">
         <property name="minimumSize">
          <size>
           <width>0</width>
           <height>1200</height>
          </size>
         </property>
         <property name="styleSheet">
          <string notr="true">background-color: rgb(255, 255, 255);</string>
         </property>
        </widget>
       </item>
      </layout>
     </widget>
    </item>
    <item>


# 1 Bild als label was ich geschaft habe aber mit einer falschen auflösung das bild ist zu gross für label es wird nur 1/4 des Bildes im label angezeigt, 3/4 sind nicht zu sehen da das Bild irgend wie zu groß  ist
w = 800
h = 400
		
# Create widget
self.label_2 =Qt. QLabel(self.label_2)
pixmap = Qt.QPixmap('image.jpg')
pixmap =pixmap.scaled(Qt.QSize(w, h), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
self.label_2.setPixmap(pixmap)
BlackJack

@ganja: Einen Thread verwenden zu wollen ist schon mal falsch, weil man die GUI nur aus dem Hauptthread verändern darf. Falls also der irgendwo gefundene Code das so macht, kann an den gleich mal vergessen.

Wenn man periodisch etwas mit der GUI machen möchte, bietet sich `QTimer` dafür an.
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

Hallo @BlackJack, du weißt das ich noch Anfänger bin aber ich glaube der code verändert nichts an meine GUI es zeigt nur Bilder in dem dafür vorgesehenen Widget, aber da bin ich mir halt nicht sicher, oben in der GUI läuft ja die stopwatch wo du und Sirius mir geholfen habt das zu realisieren, ich will einfach Bilder anzeigen in der schleife in bestimmten widget graphicsView ist mein Ansatz dann komplett falsch mit dem gefundenen codefalsch, sollte ich mich den liber für label entscheiden, da der code nur die Bilder anzeigt dachte ich das wäre richtig für mich, Thread dachte ich weil oben in der GUI stopwatch läuft und unten halt die Bilder mit dem gefundenen code
BlackJack

@ganja: Ich weiss nicht ob wir jetzt gerade aneinander vorbeirreden: Der Code den Du da zeigst, startet einen Thread mit dem `threading`-Modul und der Code in diesem Thread versucht die GUI zu ändern. Das ist falsch! Falls das überhaupt funktioniert, dann höchstens zufällig und keinesfalls robust und zuverlässig, weil man die GUI nur aus dem Thread verändern darf in dem die GUI-Hauptschleife läuft. GUIs und Threads vertragen sich nicht so ohne weiteres.

Wenn man also etwas mit Threads macht, dann muss man die auf das reine ”arbeiten” im Hintergrund beschränken. Man darf dort nichts mit der GUI anstellen, die ja von der Hauptschleife schon manipuliert wird/werden kann.

Ich würde grundsätzlich immer erst einmal versuchen ohne Threads auszukommen, mit den jeweiligen Werkzeugen die das verwendete GUI-Rahmenwert zur Integration von Code in die Hauptschleife bietet. Bei Tk ist das `after()` und `after_idle()` und bei Qt ist das die `QTimer`-Klasse.

Wenn man bei Qt mit Threads arbeiten möchte die nicht reine Hintergrundthreads sind, sondern die auch mit der GUI (indirekt) interagieren sollen, dann solltest Du in der Qt-Dokumentation die Artikel über Threadprogrammierung lesen und `QThread` anschauen.
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

Ok, dann werde ich erstmal lesen und probieren mit QTimer, jetzt habe ich mal versucht 1 Bild anzuzeigen, das Bild sollte mein Label oder grapsicsView widget ausfüllen das klappt noch nicht ist das überhaupt möglich das man das Bild auf die entsprechende Grösse von dem Label skaliert mein label ist 800x1300 siehe xml code und mein Bild sollte dann auf 800x 1300 skaliert werden, , oder gehe ich das falsch an, geht das gar nicht, wie groß soll das Bild sein damit das label ausgefühlt wird, im moment ist das Bild 3264x1836 das verstehe ich nicht, kann man. das irgendwie berechnen.

Danke im Voraus

Code: Alles auswählen

self.label_3.setPixmap(QtGui.QPixmap(os.getcwd() + "/Image.jpg").scaled(800, 1300, QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation))
[codebox=xml file=Unbenannt.xml]
<widget class="QLabel" name="label_3">
<property name="minimumSize">
<size>
<width>0</width>
<height>1300</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
</property>
<property name="text">
[/code]
BlackJack

@ganja: Dein Label ist *minimal* 0 Pixel breit und 1300 Pixel hoch. Das kann also durchaus grösser werden. Oder schmaler als 800 Pixel. Wo hast Du die Zahlen der aktuellen Bildabmessung denn her? Prüf doch gleich nach dem skalieren und vor dem setzen der Pixmap deren Grösse noch mal explizit. Stimmt es dann? Hast Du beim Label eventuell die `scaledContent`-Eigenschaft gesetzt?
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

Hallo @BlackJack, ja mein Label kann schmaler werden(im Moment, es wird aber eine absolute w und h bekommen) aber das widget in dem das Label ist ist max 800, die Bild Grösse habe ich aus Bild Eigenschaften ausgelesen, scaledContent habe ich nicht gesetzt, die Grösse des Bildes prüfe ich nicht vor dem skalieren, wird doch komplizierter als ich es mir gedacht habe
sebastian0202
User
Beiträge: 168
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

@BlackJack
Das der GUI-Thread nur die GUI selber ändern darf ist ja plausibel.
Realisiert wird das ja dann über Methoden in der GUI Klasse.

Was mir aber nicht klar ist, enthält die GUI Klasse dann auch die Referenzen zu den allen QLabel, QPushButton, QFrame, .. ?
Speichert man dann alle Labels in einem Dictionary? Sowas wie self.widgets = { 'headline': QLabel()}
Oder sollte man dafür eigens ein Objekt erzeugen das sich darum kümmert, wovon die GUI letztendlich erbt?
Mir ist die Organisation nicht ganz klar.

Die Beispiele die ich gefunden habe speichern die Objekte immer nur in Variablen wie self.label1, self.label2, .. .
Aber selbst bei kleineren Projekten hat man später so viele davon, dass sieht ja grausam aus.
Und wenn die GUI dann noch mehrere Ansichten besitzt sprengt das den Rahmen.
BlackJack

@ganja: Mir ist nicht so ganz klar wie Du vorgegangen bist. Hast Du die Pixmap skaliert und dann geprüft wie gross die skalierte Pixmap ist *ohne vorher irgendetwas anderes mit der skalierten Pixmap zu machen* und die hat dann nicht die Grösse die Du für das skalieren angegeben hast? Wie gross ist die unskalierte Pixmap?

An der Stelle fällt mir auf: Pfade mit `os.path.join()` zusammen setzen, nicht mit ``+``.

@sebastian0202: Ich verstehe Deine Frage nicht wirklich und vor allem hat die mit *diesem* Thema nicht wirklich etwas zu tun, oder‽

Du findest viele Attribute grausam, aber das gleiche in einem Wörterbuch nicht? Die Antwort ist jedenfalls, das man ”statische” GUI-Elemente die man im Laufe des Programms noch verändern möchte, als Attribute speichert. Wie denn auch sonst? Das sind Eigenschaften/Zustand des entsprechenden GUI-Objekts.

Falls das zu viele Attribute werden, dann hat man entweder auch unnützes ans Objekt gebunden (die Label wollte man zum Beispiel vielleicht gar nicht als Attribute haben), oder die Klasse macht zu viel. Oder beides.

Wenn man die GUI per Qt-Designer erstellt und die *.ui-Datei dynamisch geladen hat, dann braucht man die im Qt-Designer erstellten Widgets ja auch gar nicht mehr im Code erstellen und an Attribute binden. Die gibt es dann ja schon auf dem Objekt das man geladen hat, oder auf dem das man als zusätzliches Argument an die `uic.loadUi()`-Funktion übergeben hat.
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

Hallo @BlackJack nur mit den 2 befehlen, die anscheinend nicht ausreichen

Code: Alles auswählen

pixmap = QtGui.QPixmap("imgjpg")
self.label.setPixmap(QtGui.QPixmap(pixmap).scaled(800, 1200, QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation))
ich glaube ich habe es so eben hin bekommen, mit dem befehl
self.label.setPixmap(QtGui.QPixmap(pixmap).scaled(800, 1200, QtCore.Qt.IgnoreAspectRatio,QtCore.Qt.SmoothTransformation))
BlackJack

Naja, das ignoriert dann aber die Seitenverhältnisse des Bildes. Wenn Du damit leben kannst…
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

hallo,
mittlerweile habe ich das hinbekommen was ich wollte, habe es mit QTimer wie von BlackJack empfohlen und Thread probiert, auf meinem pc läuft alles wie es sein soll im oberen teil meiner gut läuft stopwatch und im unterem eine slideshow, ob mit QTimer oder Thread läuft alles flüssig und sauber, wenn ich es aber auf dem raspberry ausführe habe ich manchmal problem das es kurze freezes gibt beim start stop reset button für die stopwatch, und die gestopte zeit passt nicht als ob irgendwo 1 Sekunde oder 2 oder 3 verschwinden der QTimer für die stopwatch läuft nicht richtig, auf dem PC kann ich es nicht nachvollziehen, ist der raspberry zu schwach um das hinzubekommen, erst habe ich gedacht die 2 QTimer 1x stopwatch 1x slideshow kommen sich in die Quere, habe dann Thread probiert keine Besserung, übersehe ich etwas!
Vielen Dank im Voraus bin dankbar für jeden tip, da es auf dem Pc oder Laptop unter Linux OSX ohne Probleme Läuft

Gruß
ganja
BlackJack

@ganja: Was sind denn das für Bilder? Grosse? Wie sieht das mit Dateisystemzugriffen aus? Die sind ja echt lahm auf dem Raspi, zwischen SD-Karte und Festplatte liegen Welten.
ganja
User
Beiträge: 189
Registriert: Mittwoch 3. Dezember 2014, 07:44

@BlackJack das sind normale Bilder 1920x1080 die sind im Moment auf der sd, ich verstehe nicht was du mit lahm meinst, den es funktioniert wie gesagt alles wie es soll auf dem Laptop, nur auf dem raspi habe ich Problem mit der stopwatch zeit, die Funktion für den slide macht alles richtig die Auslastung der CPU lag bei max 20%, Speicher schien auch in Ordnung zu sein habe es stunden lang laufen lassen, das mit dem freez des Buttons für die stopwatch war am Anfang und auch nach dem es stundenlang lief, Funktion slide ändert die Bilder im Label, stopwatch Button die zeit im Label beide in der selben GUI, mir kommt es so vor als ob der QTimer der Funktion slide sich mit dem QTimer von stopwatch überschneidet, so das dann der Timer der stopwatch falsche zeit anzeigt weniger wie es eigentlich sein sollte nur auf dem raspi
BlackJack

@ganja: Mit lahm meine ich die Zugriffe auf die SD-Karte am Raspi. Ansonsten kommen sich die beiden QTimer selbstverständlich in die Quere, denn da läuft ja nichts parallel. Es kann immer nur eine der beiden Funktionen ausgeführt werden, solange muss die andere warten, wie alles andere auch. Es sei denn Du löst das mit Threads, allerdings dann auch richtig, dass heisst Du musst bei Qt darauf achten zu welchem Thread die Qt-Objekte ”gehören”. Änderungen an der GUI dürfen trotzdem nur im Hauptthread durchgeführt werden. Was auch noch eine Rolle spielen kann ist die ”Grafikkarte”, also wieviel an der Front von der Hardware übernommen wird, und was die CPU leisten muss.

Ich denke so allgemein kann man da nichts weiter zu sagen ohne konkreten Code zu sehen und mal ausprobieren zu können.
Antworten