I'm trying to create a QSlider-QTextEdit synchronised pair, such that any change in one is reflected in the other. This is the code I have:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class MainWindow(QMainWindow):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.main_panel = QWidget(self)
self.setCentralWidget(self.main_panel)
grid = QGridLayout(self.main_panel)
self.sl = QSlider(Qt.Horizontal, self)
self.txt = QTextEdit(self)
self._old_val = ""
self.sl.valueChanged.connect(self._sl_to_txt)
self.txt.textChanged.connect(self._txt_to_sl)
self.sl.setMinimum(1)
self.sl.setMaximum(60)
self.sl.setValue(30)
grid.addWidget(self.sl, 0, 0)
grid.addWidget(self.txt, 0, 1)
def _sl_to_txt(self, val):
print("SL->TXT")
self.txt.setText(str(val))
def _txt_to_sl(self):
print("TXT->SL")
val = self.txt.toPlainText()
try:
self.sl.setValue(int(val))
self._old_val = val
except ValueError:
self.txt.setText(self._old_val)
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
However, this causes both signals to be emitted at the same time (due to setValue and setText triggering the respective signals) and also for some reason keeps placing the cursor to the start of the text area when trying to type into it.
How do I change the text/slider value without triggering the corresponding signals?
Related
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_())
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())
I want to get handle in QDockWidget(red line show in picture),So that when handle was click, move, or mouse event and so on.I want to catch it and then do something else,But I can't find how to get it,Can someone know how to get it?
Code Sample
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class View(QMainWindow):
def __init__(self):
super().__init__()
self.addDockWidget(Qt.LeftDockWidgetArea, QDockWidget("abc", self))
self.setCentralWidget(QPushButton("ABC"))
app = QApplication([])
v = View()
v.show()
app.exec()
Below Is New Update
The view in the QDockWidget,When mouse drag handle,I want to the view always show global mini map of the scene,but use re-szie event will be very slow.So I want to get mouse-release event,then use fitInView redraw mini map.
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import random
import math
r = lambda : random.randint(0, 255)
r255 = lambda : (r(), r(), r())
class Scene(QGraphicsScene):
def __init__(self):
super().__init__()
for i in range(1000*300):
item = QGraphicsEllipseItem()
item.setRect(0, 0, r(), r())
item.setBrush(QColor(*r255()))
item.setPos(r()*100, r()*100)
self.addItem(item)
class MainView(QGraphicsView):
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
super().wheelEvent(event)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.scene = Scene()
self.main = MainView()
self.main.setDragMode(QGraphicsView.ScrollHandDrag)
self.side = QGraphicsView()
self.side.setBackgroundBrush(Qt.black)
self.main.setScene(self.scene)
self.side.setScene(self.scene)
self.setCentralWidget(self.main)
dock = QDockWidget()
dock.setWidget(self.side)
self.addDockWidget(Qt.LeftDockWidgetArea, dock)
#
dock.installEventFilter(self)
def eventFilter(self, w: 'QObject', e: 'QEvent') -> bool:
if isinstance(e, QResizeEvent):
self.side.fitInView(self.scene.itemsBoundingRect(), Qt.KeepAspectRatio)
return super().eventFilter(w, e)
app = QApplication([])
v = MainWindow()
v.show()
app.exec()
I have created an app and on one of the Windows (CreateProjectWindow(QDialog)) I complete a form which I then submit using self.buttonBox.accepted.connect(self.getInfo).
On submission, I want the self.tableComboBox in UpdateProjecWindow(QDialog) automatically and immediately updated without me having to first restart the application. The last four lines of code in CreateProjectWindow(QDialog) is all I have tried but none of them works. Below is snippet of the code:
from __future__ import print_function
from datetime import date
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
import os
import subprocess
import pandas as pd
import time
Class CreateProjectWindow(QDialog):
def __init__(self):
super(CreateProjectWindow, self).__init__()
self.setWindowTitle("Create a new project")
self.setGeometry(100, 100, 300, 400)
self.timeStampLineEdit = QDateTime.currentDateTime().toString('MM-dd-yyyy hh:mm:ss')
self.surnameLineEdit = QLineEdit()
self.firstnameLineEdit = QLineEdit()
self.dateOfBirthLineEdit = QDateEdit(calendarPopup=True, displayFormat='MM-dd-yyyy')
self.createForm()
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self.getInfo)
self.buttonBox.rejected.connect(self.reject)
def getInfo():
output = {'timestamp': self.timeStampLineEdit,\
'surname' : self.surnameLineEdit,\
'firstname' : self.firstnameLineEdit,\
'dob' : self.dateOfBirthLineEdit}
df = pd.DataFrame(output, index=[0])
df.to_excel('to/some/local/path/profiles_data.xlsx')
return None
QApplication.processEvents() # attempt 1 but doesn't work
QApplication(sys.argv).reload() # attempt 2 but doesn't work
subprocess.Popen([sys.executable, FILEPATH]) # attempt 3 restarts the app which I don't want
os.execl(sys.executable, sys.executable, * sys.argv) # attempt 4 restarts the app which I don't want.
def creatForm(self):
layout = QFormLayout()
layout.addRow(QLabel("Surname"), self.surnameLineEdit)
layout.addRow(QLabel("First Name"), self.firstnameLineEdit)
layout.addRow(QLabel("D.O.B"), self.dateOfBirthLineEdit)
self.formGroupBox.setLayout(layout)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.buttonBox)
self.setLayout(mainLayout)
class UpdateProjectWindow(QDialog):
def __init__(self):
super(UpdateProjectWindow, self).__init__()
self.setWindowTitle("Parameter Inputs")
self.setGeometry(100, 100, 300, 400)
self.formGroupBox = QGroupBox("Some Window")
self.tableComboBox = QComboBox()
df = pd.read_excel('to/some/local/path/profiles_data.xlsx')
names = df['surname']
self.tableComboBox.addItems(names)
createForm()
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self.processInfo)
self.buttonBox.rejected.connect(self.reject)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.formGroupBox)
mainLayout.addWidget(self.buttonBox)
self.setLayout(mainLayout)
def processInfo(self):
get_name = self.tableComboBox.currentText()
print(get_name)
def createForm(self):
layout = QFormLayout()
layout.addRow(QLabel("Select Surname"), self.tableComboBox)
self.formGroupBox.setLayout(layout)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# setting window title
self.setWindowTitle("MyApp")
# setting geometry to the window
self.setGeometry(200, 200, 400, 10)
self.window1 = CreateProjectWindow()
self.window2 = UpdateProjectWindow()
l = QVBoxLayout()
button1 = QPushButton("Create a Project")
button1.clicked.connect(
lambda checked: self.toggle_window(self.window1)
)
l.addWidget(button1)
button2 = QPushButton("Update a Project")
button2.clicked.connect(
lambda checked: self.toggle_window(self.window2)
)
l.addWidget(button2)
w = QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def toggle_window(self, window):
if window.isVisible():
window.hide()
else:
window.show()
if __name__=="__main__":
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
There is normally no need to restart a Qt application, and certainly this is not required for your case: restarting the QApplication is almost like closing the program and opening it again, doing that just to update some data doesn't make any sense at all.
A possible solution is to create a custom signal for the first window class, which will be emitted when the database is updated, then connect that signal to a function in the other window that loads the data (which means that that function will also be called at the beginning).
class CreateProjectWindow(QDialog):
dataChanged = pyqtSignal()
# ...
def getInfo(self):
# note that your original data was completely wrong, as you tried to add
# the *widgets* to the frame, while you need to add their *values*
output = {'timestamp': self.timeStampLineEdit,
'surname' : self.surnameLineEdit.text(),
'firstname' : self.firstnameLineEdit.text(),
'dob' : self.dateOfBirthLineEdit.date().toString()}
df = pd.DataFrame(output, index=[0])
df.to_excel('to/some/local/path/profiles_data.xlsx')
self.dataChanged.emit()
self.accept()
class UpdateProjectWindow(QDialog):
def __init__(self):
# ...
self.tableComboBox = QComboBox()
self.updateData()
self.createForm()
# ...
def updateData(self):
self.tableComboBox.clear()
df = pd.read_excel('to/some/local/path/profiles_data.xlsx')
names = df['surname']
self.tableComboBox.addItems(names)
class MainWindow(QMainWindow):
def __init__(self):
# ...
self.window1.dataChanged.connect(self.window2.updateData)
Besides that, you must be much more careful with your code: I spent more time to correct your syntax and bugs than to actually give you this answer. Note that there are various other problems with your example, but I will not address them here as it's off topic. In essence, you must put more attention when writing programs.
I am having trouble understanding why a deep copy of a dictionary reverts back to original values when I run the revert() method once, but when I change values again, and run the revert() method again it changes the copied dictionaries values along with the original.
The dictionary/values are being imported from another file. I can only assume it is because the Test is being imported more than once.
Can someone help explain why this is happening and what can I do to avoid it?
Test.py
import ujson,copy
nested1 = {'1':None, '2':"String"}
nested2 = {'1':0.5123, '2':515}
diction = {'1':nested1, '2':nested2}
copyDiction = ujson.loads(ujson.dumps(diction))
copyOtherDiction = copy.deepcopy(diction)
Main.py
import Test
from PyQt5 import QtCore, QtWidgets, QtGui
class Window(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(Window,self).__init__(parent)
widget = QtWidgets.QWidget()
self.setCentralWidget(widget)
layout = QtWidgets.QVBoxLayout()
self.lineEdit = QtWidgets.QLineEdit()
self.button = QtWidgets.QPushButton("Change Value")
self.button.clicked.connect(self.changeMethod)
self.revertbutton = QtWidgets.QPushButton("Revert")
self.revertbutton.clicked.connect(self.revert)
layout.addWidget(self.lineEdit)
layout.addWidget(self.button)
layout.addWidget(self.revertbutton)
widget.setLayout(layout)
def changeMethod(self):
text = self.lineEdit.text()
if text.isdigit():
Test.diction['1']['2'] = int(text)
else:
Test.diction['1']['2'] = text
def revert(self):
print(Test.diction)
Test.diction = Test.copyDiction
print(Test.diction)
print(Test.copyDiction)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
In the following example, when you click the button, the entire form is rebuilt adding a new label each time. At the end is a resize call that doesn't appear to work. While debugging, I validated the sizeHint() is returning the correct dimensions, and internally the widget thinks it is the correct size, but what is drawn is not correct. What can I do to force the MDI window to resize correctly? Also of note, when not sized correctly, if you manually start resizing, it suddenly snaps to the appropriate size.
import sys
import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import PyQt4.Qt
class MdiWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.count = 0
self.buildWindow()
def buildWindow(self):
main = QVBoxLayout()
button = QPushButton('Change Count')
button.clicked.connect(self.changeCount)
main.addWidget(button)
for i in range(self.count):
main.addWidget(QLabel(str(i)))
widget = QWidget()
widget.setLayout(main)
self.setCentralWidget(widget)
self.resize(main.sizeHint())
def changeCount(self, event):
self.count += 1
self.buildWindow()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Resize Test')
self.mdiArea = QMdiArea()
self.setCentralWidget(self.mdiArea)
child = MdiWindow()
self.mdiArea.addSubWindow(child)
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())