首页 > 解决方案 > 如何使用 MouseMoveEvent 从 QColorDialog 更改颜色

问题描述

当我单击QPushButton时,QColorDialog会打开一个。我的问题是:我如何改变QWidgetinmouseMoveEvent的颜色QColorDialog

from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys

class ColorPicker(QColorDialog):
    def __init__(self):
        super().__init__()

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.color = None
        self.colorpicker = ColorPicker()
        self.colorChooser = QPushButton("ColorChooser", self)
        self.colorChooser.clicked.connect(self.onColorPicker)
        self.colorChooser.move(10, 10)

    def onColorPicker(self):
        self.get_color = self.colorpicker.getColor()
        self.setStyleSheet("background-color:%s;" % self.get_color.name())

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mw = MainWindow()
    mw.setFixedSize(400, 400)
    mw.show()
    sys.exit(app.exec_())

标签: pythonpyqt5

解决方案


tl;博士 - 不,你不能

不幸的是,QColorDialog 不支持这种交互。标准对话框尝试使用系统的本机颜色选择器对话框,它不提供任何 API,除了返回选定的颜色,而“非本机”对话框只有私有方法和对象,这些方法和对象不容易从 PyQt 访问。

我认为您需要的可以使用非本地对话框来实现,但这非常困难,因为应该“浏览”对话框的子项,找到“彩色淋浴”小部件,然后安装一个事件过滤器来获取它的 mouseMoveEvents,然后使用grab()orrender()获取小部件屏幕截图,将其转换为 QImage 并获取像素的颜色。但这并不安全,主要是因为它是一个“硬编码”对话框,它的内容将来可能会发生变化;长话短说,在 Qt 的不同版本(甚至是次要版本)中,这可能无法按预期工作。此外,由于彩色淋浴有一个“十字光标”来显示当前颜色,你可能会冒险,通过将鼠标悬停在它上面,你只会得到十字光标的颜色,

替代解决方案:创建自己的颜色选择器

自定义颜色选择器示例的屏幕截图

前段时间我创建了一个更复杂的颜色选择工具,因为我对 Qt 提供的功能不满意:我主要在 Linux 上工作(它没有实际的原生颜色对话框),我需要在 Windows 和MacOS,加上 Qt4 版本的 QColorDialog 所具有的 Qt5 对应物(反之亦然)所缺少的其他东西。
幸运的是,我已经能够为此回收其中的一部分,因为我发现了彩色淋浴是如何绘制的以及如何使用鼠标获得颜色。请注意,在我的工具中,我还创建了一个“色轮”(中间带有渐变三角形的色环),但这有点复杂。
此示例将显示一个带有颜色选择器的小对话框,并自动将其颜色设置为当前颜色。移动鼠标会自动更新主窗口小部件的背景,但只有当对话框被接受时才会应用(通过单击 Ok 或按 Enter/Return)。

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class RgbPicker(QtWidgets.QLabel):
    # create a vertical color gradient similar to the "Color Shower"
    # used in QColorDialog
    colorGrads = QtGui.QLinearGradient(0, 0, 1, 0)
    colorGrads.setCoordinateMode(colorGrads.ObjectBoundingMode)
    xRatio = 1. / 6
    colorGrads.setColorAt(0, QtCore.Qt.red)
    colorGrads.setColorAt(1, QtCore.Qt.red)
    colorGrads.setColorAt(xRatio, QtCore.Qt.magenta)
    colorGrads.setColorAt(xRatio * 2, QtCore.Qt.blue)
    colorGrads.setColorAt(xRatio * 3, QtCore.Qt.cyan)
    colorGrads.setColorAt(xRatio * 4, QtCore.Qt.green)
    colorGrads.setColorAt(xRatio * 5, QtCore.Qt.yellow)

    # add a "mask" gradient to support gradients to lighter colors
    maskGrad = QtGui.QLinearGradient(0, 0, 0, 1)
    maskGrad.setCoordinateMode(maskGrad.ObjectBoundingMode)
    maskGrad.setColorAt(0, QtCore.Qt.transparent)
    maskGrad.setColorAt(1, QtCore.Qt.white)

    # create a cross cursor to show the selected color, if any
    cursorPath = QtGui.QPainterPath()
    cursorPath.moveTo(-10, 0)
    cursorPath.lineTo(-4, 0)
    cursorPath.moveTo(0, -10)
    cursorPath.lineTo(0, -4)
    cursorPath.moveTo(4, 0)
    cursorPath.lineTo(10, 0)
    cursorPath.moveTo(0, 4)
    cursorPath.lineTo(0, 10)
    cursorPen = QtGui.QPen(QtCore.Qt.black, 3)

    colorChanged = QtCore.pyqtSignal(QtGui.QColor)
    showCursor = False
    cursorPos = QtCore.QPoint()

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMouseTracking(True)
        self.setFixedSize(220, 200)
        # create a pixmap and paint it with the gradients
        pixmap = QtGui.QPixmap(self.size())
        qp = QtGui.QPainter(pixmap)
        qp.fillRect(pixmap.rect(), self.colorGrads)
        qp.fillRect(pixmap.rect(), self.maskGrad)
        qp.end()
        self.setPixmap(pixmap)
        # a QImage is required to get the color of a specific pixel
        self.image = pixmap.toImage()
        self.currentColor = QtGui.QColor()

    def setColor(self, color):
        self.currentColor = color
        # compute the cursor coordinates according to the color values;
        # this is based on Hue/Saturation/Value data of the color
        h, s, v, a = color.getHsv()
        x = (360 - h) * (self.width() - 1) / 360.
        y = (255 - s) * (self.height() - 1) / 255.
        self.cursorPos = QtCore.QPoint(x, y)
        self.showCursor = True
        self.update()

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            # set the current color and emit the colorChanged signal
            self.currentColor = QtGui.QColor(self.image.pixel(event.pos()))
            self.cursorPos = event.pos()
            self.showCursor = True
            self.update()

    def mouseMoveEvent(self, event):
        if event.pos() in self.rect():
            color = QtGui.QColor(self.image.pixel(event.pos()))
            self.colorChanged.emit(color)
            if event.buttons() == QtCore.Qt.LeftButton:
                # if the left button is pressed, update the current color
                self.currentColor = color
                self.cursorPos = event.pos()
                self.update()

    def leaveEvent(self, event):
        # emit the previously selected color when leaving
        self.colorChanged.emit(self.currentColor)

    def paintEvent(self, event):
        # paint the "color shower"
        QtWidgets.QLabel.paintEvent(self, event)
        if self.showCursor:
            # paint the color "cursor"
            qp = QtGui.QPainter(self)
            qp.setPen(self.cursorPen)
            qp.translate(self.cursorPos)
            qp.drawPath(self.cursorPath)


class ColorPicker(QtWidgets.QDialog):
    colorChanged = QtCore.pyqtSignal(QtGui.QColor)
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)

        self.rgbPicker = RgbPicker(self)
        layout.addWidget(self.rgbPicker)
        self.rgbPicker.colorChanged.connect(self.colorChanged)

        buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
        layout.addWidget(buttonBox)
        buttonBox.accepted.connect(self.accept)
        buttonBox.rejected.connect(self.reject)

    def getColor(self, color=None):
        if isinstance(color, QtGui.QColor):
            self.rgbPicker.setColor(color)
        # return a color only if the dialog is accepted
        if self.exec_():
            return self.rgbPicker.currentColor

class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        # get the current background color, should we ignore the picker selection
        self.color = self.palette().color(QtGui.QPalette.Window)
        self.colorPicker = ColorPicker(self)
        self.colorPicker.colorChanged.connect(self.setcolorChanged)
        self.colorChooser = QtWidgets.QPushButton("ColorChooser", self)
        self.colorChooser.clicked.connect(self.onColorPicker)
        self.colorChooser.move(10, 10)

    def setcolorChanged(self, color):
        # set the stylesheet *only* for this class, not its children, otherwise
        # you'll set the background for both the button *and* the color picker
        self.setStyleSheet("MainWindow { background-color:%s;}" % color.name())

    def onColorPicker(self):
        color = self.colorPicker.getColor(self.color)
        # update the color only if the dialog is accepted: if the user presses
        # Esc, it will be ignored
        if color:
            print('ok', color.getRgb())
            self.color = color
        self.setcolorChanged(self.color)

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

推荐阅读