devxen21
devxen21

Reputation: 29

How to use QStackedWidget()

Am trying to use QStackedWidget() to switch to my next window but when i do that i get some errors that don't have when i run my ".py" files separately.

what my app should do is ... activate my group box with a click, then if i click the button, a new transparent window should pop-up with a mouse-cross listener, then when you click something it should stops returning the mouse cursor to normal and closing the transparent window.

window1.py

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QColor, QKeySequence, QIcon, QCursor
from window2 import *

class Ui_Form(QtWidgets.QWidget):
    def __init__(self):
        super(Ui_Form, self).__init__()

    ##if i use this method, it does not show my transparent window, also it shows an error that i dont get when run it separately.

    def goToTransparentWindowMethod(self):
        self.goToTransparentWindow = TransparentWindowClass()
        myWindow.addWidget(self.goToTransparentWindow)
        myWindow.setCurrentIndex(myWindow.currentIndex()+1)

    def setupUi(self, myWindow):
        myWindow.setObjectName("myWindow")
        myWindow.resize(627, 327)

        self.horizontalLayoutWidget = QtWidgets.QWidget(myWindow)
        self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 300, 270))
        self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")

        self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout.setObjectName("horizontalLayout")

        self.gpb_main = QtWidgets.QGroupBox(self.horizontalLayoutWidget)
        self.gpb_main.setCheckable(True)
        self.gpb_main.setChecked(False)
        self.gpb_main.setObjectName("gpb_spell_main")

        self.btn_main = QtWidgets.QPushButton(self.gpb_main)
        self.btn_main.setGeometry(QtCore.QRect(10, 40, 88, 27))
        self.btn_main.setObjectName("btn_main")
        self.btn_main.clicked.connect(self.goToTransparentWindowMethod)

        self.horizontalLayout.addWidget(self.gpb_main)

        self.retranslateUi(myWindow)
        QtCore.QMetaObject.connectSlotsByName(myWindow)

    def retranslateUi(self, myWindow):
        _translate = QtCore.QCoreApplication.translate
        myWindow.setWindowTitle(_translate("myWindow", "Window1"))
        self.gpb_main.setTitle(_translate("myWindow", "example"))
        self.btn_main.setText(_translate("myWindow", "click me"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    myWindow = QtWidgets.QStackedWidget()
    ui = Ui_Form()
    ui.setupUi(myWindow)
    myWindow.show()
    sys.exit(app.exec_())

window2.py

from PyQt5 import QtCore, QtGui, QtWidgets
from pynput.mouse import Listener
import pyautogui


class TransparentWindowThreadClass(QtCore.QObject):

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

    @QtCore.pyqtSlot()
    def on_click_main(self, x, y, button, pressed):
        try:
            if pressed:
                self.pos_main = pyautogui.position()
                self.get_rgb_main = pyautogui.pixel(self.pos_main[0], self.pos_main[1])
                r = self.get_rgb_main.red
                g = self.get_rgb_main.green
                b = self.get_rgb_main.blue
                self.get_rgb_main = r,g,b

                Transparent_Window.unsetCursor()#error here when is called from window1
                Transparent_Window.close()#error here when is called from window1
             
                #Pressed
                self.pressed_Msg = 'Pressed at x:{0}  y:{1} RGB:{2}'.format(x, y, self.get_rgb_main)
                print(self.pressed_Msg)
            else: 
                #Released msg
                self.released_Msg = 'Released at x:{0} y:{1} RGB:{2}'.format(x, y, self.get_rgb_main)
                print(self.released_Msg)

            if not pressed:
                return False

        except KeyboardInterrupt:
            print('you pressed ctrl+c')
        except NameError:
            print("Error on_click_main")
        except RuntimeError:
            print('run time error')
        except TypeError:
            print('ype error')
        except AttributeError:
            print('Attribute Error')

    @QtCore.pyqtSlot()
    def loop_onclick_listener(self):
        try:
            with Listener(on_click=self.on_click_main) as listener:             
                listener.join()
        except KeyboardInterrupt:
            print('you pressed ctrl+c')
        except NameError:
            print("Error onclick_listener")
        except RuntimeError:
            print('run time error')
        except TypeError:
            print('ype error')
        except AttributeError:
            print('Attribute Error')

class TransparentWindowClass(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(TransparentWindowClass, self).__init__()

        self.monitorResolution = pyautogui.size()

        # create a QThread and start the thread that handles
        self.thread = QtCore.QThread()
        self.thread.start()

        # create the worker without a parent so you can move it
        self.worker = TransparentWindowThreadClass()## my custom thread class
        # the worker is moved to another thread
        self.worker.moveToThread(self.thread)

        # if the thread started, connect it
        self.thread.started.connect(self.worker.loop_onclick_listener)


    def setupUi(self, Transparent_Window):
        Transparent_Window.setObjectName("Transparent_Window")
        Transparent_Window.resize(self.monitorResolution[0], self.monitorResolution[1])
        Transparent_Window.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        Transparent_Window.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
        Transparent_Window.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))

        self.retranslateUi(Transparent_Window)
        QtCore.QMetaObject.connectSlotsByName(Transparent_Window)

    def retranslateUi(self, Transparent_Window):
        _translate = QtCore.QCoreApplication.translate
        Transparent_Window.setWindowTitle(_translate("Transparent", "Transparent Window"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Transparent_Window = QtWidgets.QWidget()
    ui = TransparentWindowClass()
    ui.setupUi(Transparent_Window)
    Transparent_Window.show()
    sys.exit(app.exec_())

Upvotes: 0

Views: 452

Answers (1)

musicamante
musicamante

Reputation: 48231

You are making things too complex.

Also, even assuming that no other possible solution could be used, you must remember that widgets are not thread-safe, and can not be directly accessed from external threads. The only safe and correct way to communicate between threads is by using signals and slots.

That said, there is no need for pyautogui nor pynput if you just want to get the color of a pixel on the screen.

If you want to grab a pixel on the screen, you can use the QScreen function grabWindow(), using 0 as window id (which matches the whole screen), and with a single-pixel area.

Then you can use grabMouse() to ensure that you always receive mouse events, even if the mouse is outside of the widget and no mouse button was being pressed. Note that grabMouse() can only work on visible widgets, so we need to "hide" the window by moving it off-screen.

Then, overriding mousePressEvent() you can use grabWindow() with the global position of the mouse, it will return a QPixmap that can be converted to a QImage and get the pixelColor() of the grab above.

The grabbing can be canceled by pressing Esc.

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

class GrabTest(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(200, 200)

        self.button = QPushButton('Grab!')

        self.colorLabel = QLabel('No color', alignment=Qt.AlignCenter)
        self.colorLabel.setFixedSize(120, 30)
        self.colorLabel.setStyleSheet('border: 1px solid black;')

        layout = QVBoxLayout(self)
        layout.addStretch()
        layout.addWidget(self.button, alignment=Qt.AlignCenter)
        layout.addWidget(self.colorLabel, alignment=Qt.AlignCenter)
        layout.addStretch()

        self.button.clicked.connect(self.startGrab)

    def mousePressEvent(self, event):
        if QWidget.mouseGrabber() == self:
            self.getPixel(event.globalPos())
        else:
            super().mousePressEvent(event)

    def keyPressEvent(self, event):
        if QWidget.mouseGrabber() == self and event.key() == Qt.Key_Escape:
            self.stopGrab()
        else:
            super().keyPressEvent(event)

    def startGrab(self):
        self.grabMouse(Qt.CrossCursor)
        self.oldPos = self.pos()
        deskRect = QRect()
        for screen in QApplication.screens():
            deskRect |= screen.geometry()
        # move the window off screen while keeping it visible
        self.move(deskRect.bottomRight())


    def stopGrab(self):
        self.releaseMouse()
        self.move(self.oldPos)

    def getPixel(self, pos):
        screen = QApplication.screens()[0]
        pixmap = screen.grabWindow(0, pos.x(), pos.y(), 1, 1)
        color = pixmap.toImage().pixelColor(0, 0)
        if color.lightnessF() > .5:
            textColor = 'black'
        else:
            textColor = 'white'
        self.colorLabel.setStyleSheet('''
            color: {};
            background: {};
        '''.format(textColor, color.name()))
        self.colorLabel.setText(color.name())
        self.stopGrab()


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    grabber = GrabTest()
    grabber.show()
    sys.exit(app.exec())

Upvotes: 2

Related Questions