Using python and bindings (pyqt, pyopengl) I have created a simple 3D viewer. I would like to create some basic actions operated/triggered by user interaction. The program has 2 parts.
opengl widget:
class OpenGLWidget(QtOpenGL.QGLWidget):
def __init__(self, parent=None):
self.parent = parent
QtOpenGL.QGLWidget.__init__(self, parent)
...
def draw(self):
#here I would like to change colour of background from right mouse click menu
glClearColor(self.R,self.G,self.B,1)
main widget:
class MainWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(initial_window_width, initial_window_height)
self.setWindowTitle('Window Name')
self.setMouseTracking(True)
# location of window on screen
self.setGeometry(5, 25, initial_window_width, initial_window_height)
self.createActions()
self.createMenus()
# sets opengl window in central widget position
self.OpenGLWidget = OpenGLWidget()
self.setCentralWidget(self.OpenGLWidget)
#pyqtSlot(QtCore.QPoint)
def contextMenuRequested(self,point):
menu = QtGui.QMenu()
action1 = menu.addAction("Blue")
self.connect(action1,SIGNAL("triggered()"), self,SLOT("Blue()"))
menu.exec_(self.mapToGlobal(point))
#pyqtSlot()
def Blue(self):
self.R = 0
self.G = 0
self.B = 1
The code that runs the entire program:
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
win.setContextMenuPolicy(QtCore.Qt.CustomContextMenu);
win.connect(win, SIGNAL("customContextMenuRequested(QPoint)"),
win, SLOT("contextMenuRequested(QPoint)"))
win.show()
sys.exit(app.exec_())
I would like to know how to change the values R, G, B in main widget that the background colour will change to blue in opengl widget.
Inside OpenGLWidget class add the following method:
def setColor(R, G, B):
self.R = R
self.G = G
self.B = B
Inside MainWindow in Blue() replace the existing code with the following one:
self.OpenGLWidget.setColor(0,0,1)
self.openGLWidget.draw() # or do whatever you want, variables are changed in `OpenGLWidget`
To set color to green, call setColor() with 0,1,0 parameters.
Related
I am building a GUI for a hand tracking app using PyQT5.
There are two windows: one 'MainWindow' which displays the camera view, and one called 'Window 2' which holds multiple checkboxes that correspond to actions that can be enabled/disabled while the program is running.
The code for the two classes is as follows:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi("window1.ui", self)
self.setWindowTitle("Tracker")
self.display_width = 640
self.display_height = 480
# start the thread
self.gesture.clicked.connect(self.gotowindow2)
self.startbutton.clicked.connect(self.startvideo)
def gotowindow2(self):
widget.setCurrentIndex(widget.currentIndex() + 1) # changes index by 1 to change page
def startvideo(self):
# Change label color to light blue
self.startbutton.clicked.disconnect(self.startvideo)
# Change button to stop
self.startbutton.setText('Stop video')
values = Window2()
hello = values.returnvalues()
print(hello)
self.thread = VideoThread(1,1,0)
self.thread.change_pixmap_signal.connect(self.update_image)
# start the thread
self.thread.start()
self.startbutton.clicked.connect(self.thread.stop) # Stop the video if button clicked
self.startbutton.clicked.connect(self.stopvideo)
def stopvideo(self):
self.thread.change_pixmap_signal.disconnect()
self.startbutton.setText('Start video')
self.startbutton.clicked.disconnect(self.stopvideo)
self.startbutton.clicked.disconnect(self.thread.stop)
self.startbutton.clicked.connect(self.startvideo)
def closeEvent(self, event):
self.thread.stop()
event.accept()
#pyqtSlot(np.ndarray)
def update_image(self, img):
"""Updates the image_label with a new opencv image"""
qt_img = self.convert_cv_qt(img)
self.image_label.setPixmap(qt_img)
def convert_cv_qt(self, img):
"""Convert from an opencv image to QPixmap"""
rgb_image = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
h, w, ch = rgb_image.shape
bytes_per_line = ch * w
convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)
p = convert_to_Qt_format.scaled(self.display_width, self.display_height, Qt.KeepAspectRatio)
return QPixmap.fromImage(p)
class Window2(QWidget):
def __init__(self):
super(Window2, self).__init__()
loadUi("window2.ui", self)
self.backbutton.clicked.connect(self.returnvalues)
self.backbutton.clicked.connect(self.gotowindow1)
self.outputbutton.clicked.connect(self.printvalues)
def printvalues(self):
print(self.returnvalues())
def gotowindow1(self):
widget.setCurrentIndex(widget.currentIndex() - 1)
def returnvalues(self):
left = self.leftclickbutton.isChecked()
scrollup = self.scrollupbutton.isChecked()
scrolldown = self.scrolldownbutton.isChecked()
checkboxvalues = [left, scrollup, scrolldown]
return checkboxvalues
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
a = MainWindow()
b = Window2()
widget.resize(1000, 600)
widget.addWidget(a)
widget.addWidget(b)
widget.show()
sys.exit(app.exec_())
My problem is that when I check the checkboxes and press the "outputbutton", it correctly displays the three values.
When these values are fetched by hello = values.returnvalues(), it is always [False, False, False]
How can the correct values get passed into class "MainWindow" stored under variable "hello"
It seems like you are not familiar with Object Oriented Programming.
values = Window2()
hello = values.returnvalues()
This block of code actually creates a new object Window2, and its checkboxes are indeed unchecked, which is why you always get [False, False, False].
The simplest (but not the best) way to solve your problem would be to give your Window2 instance to your mainWindow.
So instead of:
a = MainWindow()
b = Window2()
you do:
b = Window2()
a = MainWindow(b)
Instead of:
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
you should do:
class MainWindow(QMainWindow):
def __init__(self, window_2):
super(MainWindow, self).__init__()
self.window_2 = window_2
and instead of:
values = Window2()
hello = values.returnvalues()
you should do:
hello = self.window_2.returnvalues()
I have an application where I have several button widgets on a QGraphicScene and I am trying to make this button widgets to make new buttons on QGraphicScene when they are clicked.
My code is as follows:
buttons = []
class SeveralButtons(QtWidgets.QWidget):
def __init__(self,id,x,y):
super(SeveralButtons,self).__init__()
self.id = id
self.x = x
self.y = y
self.setGeometry(x,y,1,1)
self.button1 = QtWidgets.QPushButton("B1")
self.button2 = QtWidgets.QPushButton("B2")
self.button1.clicked.connect(self.p1b)
self.severalButtonsLayout = QtWidgets.QGridLayout()
self.severalButtonsLayout.addWidget(self.button1, 0,0,)
self.severalButtonsLayout.addWidget(self.button2, 0,1,)
self.setLayout(self.severalButtonsLayout)
def p1b(self):
ph = SeveralButtons(0,self.x-200,self.y-200)
buttons.append(ph)
UiWindow._scene.addWidget(ph)
And my main class is like this:
class UiWindow(QtWidgets.QMainWindow):
factor = 1.5
def __init__(self, parent=None):
super(UiWindow, self).__init__(parent)
self.setFixedSize(940,720)
self._scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(self._scene)
self._view.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self._view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.initButtons()
self.setCentralWidget(self._view)
def initButtons(self):
self.p = SeveralButtons(0,500,500)
buttons.append(self.p)
self._scene.addWidget(self.p)
def updateButtons(self,phrase):
for b in buttons:
if b != buttons[0]:
self._scene.addWidgets(b)
# ADD BUTTON WIDGET IN buttons ARRAY TO THE _scene OBJECT
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = UiWindow()
ui.show()
sys.exit(app.exec_())
As it is shown in here I am trying to update widgets in main window with button click but I get QGraphicsProxyWidget::setWidget: cannot embed widget 0x24ac1a93000; already embedded error.
How can I overcome this problem or what is the sane way to make this work? My main goal in this program is that every button group can create their children button group when clicked. Doing this with classes is way to go or should I stick to methods in main window class when creating recursive widgets?
Thanks in advance.
EDIT:
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1)
...
def b1(self):
sb = SeveralButtons()
buttons.append(sb)
self.b1Signal.emit()
class UiWindow(QtWidgets.QMainWindow):
def __init__():
...
self.sb1 = SeveralButtons()
buttons.append(sb1)
self._scene.addWidget(self.sb1)
self.sb1.b1Signal.connect(self.updateButtons)
def updateButtons():
for b in buttons:
if b != buttons[0]:
self._scene.addWidget(b)
The SeveralButtons class should not be responsible of creating new buttons outside itself.
You should emit that signal and connect it to the function of its parent, which will then create a new instance of the same class and also connect the signal.
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1Signal)
layout.addWidget(self.button1)
class UiWindow(QtWidgets.QMainWindow):
def __init__():
# ...
self.buttons = []
self.addButtons()
def addButtons():
newButtons = SeveralButtons()
newButtons.b1Signal.connect(self.addButtons)
self.buttons.append(newButtons)
self._scene.addWidget(newButtons)
There have been similar questions asked about overriding the QCompleter popup position but i'll still not found a working solution. I simply want to move the popup down around 5px (I have some specific styling requirements)
I've tried subclassing a QListView and using that as my popup using setPopup(). I then override the showEvent and move the popup down in Y. I also do this on the resizeEvent since I believe this is triggered when items are filtered and the popup resizes. However this doesn't work.. I then used a singleshot timer to trigger the move after 1ms. This does kind of work but it seems quite inconsistent - the first time it shows is different to subsequent times or resizing.
Below is my latest attempt (trying to hack it by counting the number of popups..), hopefully someone can show me what i'm doing wrong or a better solution
import sys
import os
from PySide2 import QtCore, QtWidgets, QtGui
class QPopup(QtWidgets.QListView):
def __init__(self, parent=None):
super(QPopup, self).__init__(parent)
self.popups = 0
def offset(self):
y = 3 if self.popups < 2 else 7
print('y: {}'.format(y))
self.move(self.pos().x(), self.pos().y() + y)
self.popups += 1
def showEvent(self, event):
print('show')
# self.offset()
QtCore.QTimer.singleShot(1, self.offset)
def resizeEvent(self, event):
print('resize')
# self.offset()
QtCore.QTimer.singleShot(1, self.offset)
class MyDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.create_widgets()
self.create_layout()
self.create_connections()
def create_widgets(self):
self.le = QtWidgets.QLineEdit('')
self.completer = QtWidgets.QCompleter(self)
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
self.completer.setMaxVisibleItems(10)
self.completer.setFilterMode(QtCore.Qt.MatchContains)
self.completer.setPopup(QPopup())
popup = QPopup(self)
self.completer.setPopup(popup)
self.model = QtCore.QStringListModel()
self.completer.setModel(self.model)
self.le.setCompleter(self.completer)
self.completer.model().setStringList(['one','two','three'])
def create_layout(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.le)
def create_connections(self):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
my_dialog = MyDialog()
my_dialog.show() # Show the UI
sys.exit(app.exec_())
One solution could be to make a subclass of QLineEdit and override keyPressEvent to display the popup with an offset:
PySide2.QtWidgets.QCompleter.complete([rect=QRect()])
For PopupCompletion and QCompletion::UnfilteredPopupCompletion modes, calling this function displays the popup displaying the current completions. By default, if rect is not specified, the popup is displayed on the bottom of the widget() . If rect is specified the popup is displayed on the left edge of the rectangle.
see doc.qt.io -> QCompleter.complete.
Complete, self-contained example
The rect is calculated based on the y-position of the cursor rect. The height of the popup window is not changed. The width is adjusted to the width of the ZLineEdit widget.
rect = QtCore.QRect(0,
self.cursorRect().y() + 4,
self.width(),
self.completer().widget().height())
Your code, slightly modified using the points mentioned above, could look like this:
import sys
from PySide2 import QtCore, QtWidgets
from PySide2.QtWidgets import QLineEdit, QDialog, QCompleter
class ZLineEdit(QLineEdit):
def __init__(self, string, parent=None):
super().__init__(string, parent)
def keyPressEvent(self, event):
super().keyPressEvent(event)
if len(self.text()) > 0:
rect = QtCore.QRect(0,
self.cursorRect().y() + 4,
self.width(),
self.completer().widget().height())
self.completer().complete(rect)
class MyDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.le = ZLineEdit('')
autoList = ['one', 'two', 'three']
self.completer = QCompleter(autoList, self)
self.setup_widgets()
self.create_layout()
self.create_connections()
def setup_widgets(self):
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setCompletionMode(QtWidgets.QCompleter.PopupCompletion)
self.completer.setMaxVisibleItems(10)
self.completer.setFilterMode(QtCore.Qt.MatchContains)
self.le.setCompleter(self.completer)
def create_layout(self):
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.addWidget(self.le)
def create_connections(self):
pass
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
my_dialog = MyDialog()
my_dialog.show()
sys.exit(app.exec_())
Test
On the left side you see the default behavior. On the right side the popup is moved down 4px:
Hi is there a way to resize one widget before another in a layout? For example, when I resize the window, I want one of the widgets to resize to zero first before resizing the other widget.
Here is what I have so far:
from PySide2 import QtCore, QtWidgets, QtGui
class TestWindow(QtWidgets.QMainWindow):
def __init__(self):
super(TestWindow, self).__init__()
wgt = QtWidgets.QWidget(self)
mainLayout = QtWidgets.QHBoxLayout()
wgt.setLayout(mainLayout)
self.setWindowTitle("Test")
l = QtWidgets.QFrame()
r = QtWidgets.QFrame()
l.setStyleSheet("background-color: blue;")
r.setStyleSheet("background-color: green;")
mainLayout.addWidget(l)
mainLayout.addWidget(r)
self.setCentralWidget(wgt)
app = QtWidgets.QApplication()
x = TestWindow()
x.show()
app.exec_()
Here are some pictures showing what I want:
Green disappears first, then blue
So in this example, I want the green box to get smaller first, before the blue one does when I resize the main window. How do I achieve this in QT?
I found a workaround for this. I decided to use a splitter for this solution.
class TitleSplitter(QtWidgets.QSplitter):
def __init__(self):
super(TitleSplitter, self).__init__()
self.setStyleSheet("::handle {background-color: transparent}")
self.setHandleWidth(0)
def resizeEvent(self, event):
""" Restrain the size """
if self.count() > 1:
w = self.widget(0).sizeHint().width()
self.setSizes([w, self.width()-w])
return super(TitleSplitter, self).resizeEvent(event)
def addWidget(self, *args, **kwargs):
""" Hide splitters when widgets added """
super(TitleSplitter, self).addWidget(*args, **kwargs)
self.hideHandles()
def hideHandles(self):
""" Hide our splitters """
for i in range(self.count()):
handle = self.handle(i)
handle.setEnabled(False)
handle.setCursor(QtCore.Qt.ArrowCursor)
handle.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
Usage:
split = TitleSplitter()
l = QtWidgets.QFrame()
r = QtWidgets.QFrame()
l.setStyleSheet("background-color: blue;")
r.setStyleSheet("background-color: green;")
split.addWidget(l)
split.addWidget(r)
It only works if you have 2 widgets added. Ideally I would probably use a layout for this, but this works well enough for me.
I am currently making a program where a user selects an image qpushbutton. I already have superseded mouseMoveEvent, mousePressEvent, and mouseReleaseEvent in the button class to get a movable button. The buttons are currently moving independently, but I would like the buttons to move so that the horizontal distance between them stays the same.
So currently in pseudo code I have:
import stuff
import mvbutton as mv
class MasterClass(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
#more setup stuff, layout, etc
self.addbutton(image,name,size)
def addbutton(#args):
self.button=mv.dragbutton(#args)
#some more setup
#now rename so that each button has its own name
if name== "name1":
self.name1=self.button
else:
self.name2=self.button
self.button=""
#more code to set up
I supersede the mouse motion/press/release functions in the dragbutton class. I cannot, therefore reference the new self.name# there. So the self.move(pos) in my dragbutton class cannot get the self.name# because it is a different self. Any ideas on how I could get this to work? Thanks.
Done something very rough after trying to understand your requirement.
Hope this helps.
EDIT
tried to add more accuracy in moving. Won't do real time moving cause it has problems with lag and update. I guess the moving won't be jittery any more.
from PyQt4 import QtGui
import sys
class MultiButton(QtGui.QWidget):
def __init__(self, *args, **kwargs):
QtGui.QWidget.__init__(self, *args, **kwargs)
self._b1 = QtGui.QPushButton("B1")
self._b2 = QtGui.QPushButton("B2")
self._arrangeWidgets()
self.setStyleSheet("background-color: rgb(0, 0, 0);\n"+\
"color: rgb(255, 255, 255);\n"+\
"border:1px solid #7F462C ;\n")
self._moveStart = False
self._startX = 0
self._startY = 0
def _arrangeWidgets(self):
layout = QtGui.QHBoxLayout()
layout.addWidget(self._b1)
#horizontal spacing remains constant now
layout.addSpacing(90)
layout.addWidget(self._b2)
self.setLayout(layout)
def mousePressEvent(self,event):
self._moveStart = True
self._startX = event.pos().x() - self.pos().x()
self._startY = event.pos().y() - self.pos().y()
return QtGui.QWidget.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
if self._moveStart:
self.setGeometry(event.pos().x() - self._startX,event.pos().y() - self._startY,self.width(),self.height())
self._moveStart = False
self._startX = 0
self._startY = 0
return QtGui.QWidget.mouseReleaseEvent(self, event)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
wd = QtGui.QMainWindow()
wd.resize(500,500)
mb = MultiButton()
mb.setFixedSize(200,50)
wd.setCentralWidget(mb)
wd.show()
sys.exit(app.exec_())
here the MultiButton widget moves the two buttons keeping the horizontal space between the two always constant.