0

如何使用 PyQt5/PySide 或任何其他 Python 库以全屏模式在辅助监视器上显示所需的图像?过去,我使用帧缓冲图像查看器(FbiFbi 改进)。但是,这种方法需要我使用 Linux。我更喜欢在 Windows 中工作,最好使用 Python 找到解决方案。

动机/背景

我正在研究基于 DLP 投影的 3D 打印过程。当我使用 HDMI 将 DLP 投影仪连接到我的 Windows PC 时,它显示为第二台显示器。我只想将这个辅助显示器 (DLP) 用于显示我想要的 3D 打印过程的图案图像(png、bmp 或 svg)。我想使用 Python 以编程方式控制正在显示的图像。这是https://3dprinting.stackexchange.com/questions/1217/how-to-display-images-on-dlp-using-hdmi-for-3d-printing的后续问题

部分解决方案和问题

下面的代码是一种可能的解决方案,但是我不确定它是正确的还是最有效的方法。我发现了两种使用 PyQt5 的方法:1)使用闪屏,2)使用 QLabel。我的代码面临以下问题:

  • 光标按预期隐藏,但是如果我不小心在辅助屏幕上单击鼠标,则启动屏幕会关闭。
  • 如果我使用 QLabel 方法,我会看到出现白屏,然后我的图像被加载。从出现白屏到显示实际图像之间存在约 0.5-1 秒的明显延迟。
  • 如果图像以高频率显示(例如:每 1 秒),则此代码无法正常工作。例如,在代码中将total_loops=1更改为total_loops=25。使用启动画面方法时,我看到启动画面出现在主屏幕上,然后它移动到辅助屏幕。使用 QLabel 方法时,我看到的只是出现白屏,并且仅显示图像的最后一次迭代。此外,QLabel 的窗口在主屏幕上变为活动状态,并且在任务栏中可见。
  • 如果我想显示视频而不是图像,我该如何处理?

对于 3D 打印应用,解决方案需要满足以下要求:

  • 辅助屏幕是 DLP 投影仪,它不应包含任何与操作系统相关的窗口/任务栏/等...
  • 辅助屏幕上不应出现光标/鼠标或其他应用程序
  • 图片/视频需要全屏显示
  • 在副屏显示或更新图像时,主屏应无干扰。例如,辅助屏幕中的图像窗口不应将焦点从主屏幕中的当前活动窗口中移开
import time
start_time = time.time() 
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QSplashScreen
from PyQt5.QtGui import QPixmap, QCursor
from PyQt5.QtCore import Qt
import os 

app = QApplication(sys.argv)

total_loops = 1

for i in range(total_loops):    

    # https://doc.qt.io/qtforpython/index.html
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/module_index.html

    s = app.screens()[1] # Get the secondary screen 
    # Display info about secondary screen 
    print('Screen Name: {} Size: {}x{} Available geometry {}x{} '.format(s.name(), s.size().width(), s.size().height(), s.availableGeometry().width(), s.availableGeometry().height()))

    # Hide cursor from appearing on screen 
    app.setOverrideCursor(QCursor(Qt.BlankCursor)) # https://forum.qt.io/topic/49877/hide-cursor 

    # Select desired image to be displayed 
    pixmap = QPixmap('test.png')

    # Splash screen approach 
    # https://doc.qt.io/qtforpython/PySide2/QtWidgets/QSplashScreen.html?highlight=windowflags 
    splash = QSplashScreen(pixmap)      # Set the splash screen to desired image
    splash.show()                       # Show the splash screen
    splash.windowHandle().setScreen(s)  # Set splash screen to secondary monitor https://stackoverflow.com/a/30597458/4988010
    splash.showFullScreen()             # Show in splash screen in full screen mode 

    # # Qlabel apporach 
    # l = QLabel()
    # l.setPixmap(pixmap)
    # l.move(1920,0)
    # l.show()
    # l.windowHandle().setScreen(s) # https://stackoverflow.com/a/30597458/4988010
    # l.showFullScreen()

    time.sleep(0.5) 
    end_time = time.time() 
    print('Execution  time: ', end_time-start_time )

sys.exit(app.exec_())
4

2 回答 2

1

下面的代码是我的问题的一种可能的解决方案。我的解决方案假定 Qt 仅用于全屏显示图像,而不用于其余逻辑。因此,我不得不在辅助线程中运行 QT 应用程序。这是因为在我运行该函数的那一刻app.exec_(),Qt 将连续运行一个事件循环,从而阻止我的其余不依赖于 Qt 的 Python 逻辑。我的理解QApplication是不建议在主线程之外运行,因此我欢迎更有经验的用户发布更好的方法。

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QObject, pyqtSignal
import sys
import time
import threading


def main():

    print('Step 1')
    print('     Some logic here without QT')

    print('Step 2')
    print('     Launch QT app to run in background')
    myapp = myImageDisplayApp()

    print('Step 3')
    print('     Continue some logic while QT running in background')
    time.sleep(2)

    print('Step 4')
    print('     Update the displayed image in the QT app running in background')
    myapp.emit_image_update('qt_test_static_1.png')
    time.sleep(2)

    print('Step 5')
    print('     Update displayed image again')
    myapp.emit_image_update('qt_test_static_2.png')
    time.sleep(2)

class myImageDisplayApp (QObject):

    # Define the custom signal
    # https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html#the-pyqtslot-decorator
    signal_update_image = pyqtSignal(str)

    def __init__ (self):

        super().__init__()

        # Setup the seperate thread 
        # https://stackoverflow.com/a/37694109/4988010
        self.thread = threading.Thread(target=self.run_app_widget_in_background) 
        self.thread.daemon = True
        self.thread.start()

    def run_app_widget_in_background(self):
        self.app = QApplication(sys.argv)
        self.my_bg_qt_app = qtAppWidget(main_thread_object=self)
        self.app.exec_()

    def emit_image_update(self, pattern_file=None):
        print('emit_image_update signal')
        self.signal_update_image.emit(pattern_file)


class qtAppWidget (QLabel):

    def __init__ (self, main_thread_object):

        super().__init__()

        # Connect the singal to slot
        main_thread_object.signal_update_image.connect(self.updateImage)

        self.setupGUI()

    def setupGUI(self):

        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen 
        self.selected_screen = 0            # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init 
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor('black'))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus | Qt.WindowStaysOnTopHint)
        # Hide mouse cursor 
        self.app_widget.setCursor(Qt.BlankCursor)       

        self.app_widget.setGeometry(0, 0, self.screen_width, self.screen_height)            # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle('myImageDisplayApp')
        self.app_widget.setAlignment(Qt.AlignLeft | Qt.AlignTop) #https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum                         
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen 
        self.app_widget.showFullScreen()


    def updateImage(self, pattern_file=None):
        print('Pattern file given: ', pattern_file)
        self.app_widget.clear()                     # Clear all existing content of the QLabel
        self.pixmap = QPixmap(pattern_file)         # Update pixmap with desired image  
        self.app_widget.setPixmap(self.pixmap)      # Show desired image on Qlabel

if __name__ == "__main__":

    main() 

我还要感谢 @ekhumoro 向我指出 QWidget 属性/标志。

于 2019-12-31T05:11:05.943 回答
0

您不应该在主线程之外运行 GUI,因为 Qt 不能保证它按照文档的指示正常工作。您必须在另一个线程中执行其他繁重的任务,而不是在另一个线程中执行 GUI。

你必须改变你对经典顺序逻辑的方法,但你必须使用面向事件的编程,在事件之前采取行动,在 Qt 通过信号的情况下。

考虑到上述情况,解决方案是:

import sys
import time


from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt, QThread, QTimer
from PyQt5.QtGui import QColor, QPixmap
from PyQt5.QtWidgets import QApplication, QLabel, QWidget


class TaskManager(QObject):
    task3Finished = pyqtSignal()
    task4Finished = pyqtSignal()

    @pyqtSlot()
    def task3(self):
        print("Step 3")
        print("     Continue some logic while QT running in background")
        time.sleep(2)
        self.task3Finished.emit()

    @pyqtSlot()
    def task4(self):
        print("Step 4")
        print("     Update the displayed image in the QT app running in background")
        time.sleep(2)
        self.task4Finished.emit()


class qtAppWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupGUI()

    def setupGUI(self):
        self.app = QApplication.instance()

        # Get avaliable screens/monitors
        # https://doc.qt.io/qt-5/qscreen.html
        # Get info on selected screen
        self.selected_screen = 0  # Select the desired monitor/screen

        self.screens_available = self.app.screens()
        self.screen = self.screens_available[self.selected_screen]
        self.screen_width = self.screen.size().width()
        self.screen_height = self.screen.size().height()

        # Create a black image for init
        self.pixmap = QPixmap(self.screen_width, self.screen_height)
        self.pixmap.fill(QColor("black"))

        # Create QLabel object
        self.app_widget = QLabel()

        # Varioius flags that can be applied to make displayed window frameless, fullscreen, etc...
        # https://doc.qt.io/qt-5/qt.html#WindowType-enum
        # https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum
        self.app_widget.setWindowFlags(
            Qt.FramelessWindowHint
            | Qt.WindowDoesNotAcceptFocus
            | Qt.WindowStaysOnTopHint
        )
        # Hide mouse cursor
        self.app_widget.setCursor(Qt.BlankCursor)

        self.app_widget.setGeometry(
            0, 0, self.screen_width, self.screen_height
        )  # Set the size of Qlabel to size of the screen
        self.app_widget.setWindowTitle("myImageDisplayApp")
        self.app_widget.setAlignment(
            Qt.AlignLeft | Qt.AlignTop
        )  # https://doc.qt.io/qt-5/qt.html#AlignmentFlag-enum
        self.app_widget.setPixmap(self.pixmap)
        self.app_widget.show()

        # Set the screen on which widget is on
        self.app_widget.windowHandle().setScreen(self.screen)
        # Make full screen
        self.app_widget.show()

    @pyqtSlot()
    def on_task3_finished(self):
        pixmap = QPixmap("qt_test_static_1.png")
        self.app_widget.setPixmap(pixmap)

    @pyqtSlot()
    def on_task4_finished(self):
        pixmap = QPixmap("qt_test_static_2.png")
        self.app_widget.setPixmap(pixmap)

        # quit application after to 2 secons
        QTimer.singleShot(2 * 1000, QApplication.quit)


def main(args):
    print("Step 1")
    print("     Some logic here without QT")

    print("Step 2")
    print("     Launch QT app to run")
    app = QApplication(args)
    myapp = qtAppWidget()

    thread = QThread()
    thread.start()

    manager = TaskManager()
    # move the QObject to the other thread
    manager.moveToThread(thread)

    manager.task3Finished.connect(myapp.on_task3_finished)
    manager.task3Finished.connect(manager.task4)
    manager.task4Finished.connect(myapp.on_task4_finished)

    # start task
    QTimer.singleShot(0, manager.task3)

    ret = app.exec_()

    thread.quit()
    thread.wait()

    del thread, app

    return ret


if __name__ == "__main__":

    sys.exit(main(sys.argv))
于 2019-12-31T08:02:10.317 回答