pyqt QGraphicsView mouse events trouble - pyqt

I'm writing an MDI application with QGraphicsView on each tab.
I add items, move them and there is a group. But there are a number of problems, such as:
a one-time allocation of objects. I can not rubber band select some objects and holding the Shift to add to the selection with other rubber band selection. this can be done for example if it is before the new allocation remember old objects and add it after the separation that was previously. but it is done through mouse events, and they do not work at all
necessary when you click on an object to do some action, but it is also done through mouse events ...
i need the mouse wheel to zoom, and then rests on the mouse events
possible for all of these actions have no ready-made solutions zalezaniya in mouse events, but all the lessons that I find - say that the only mouse events will save me
how to make a QGraphicsView to catch mouse events?
import os
import sys
import sip
import maya.OpenMayaUI as mui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
#----------------------------------------------------------------------
def getMayaWindow():
ptr = mui.MQtUtil.mainWindow()
return sip.wrapinstance(long(ptr), QObject)
#----------------------------------------------------------------------
#----------------------------------------------------------------------
class MainForm(QMainWindow):
def __init__(self):
super(MainForm, self).__init__(getMayaWindow())
self.setGeometry(50,50,600,600)
widget = QWidget()
self.setCentralWidget(widget)
layout = QGridLayout()
widget.setLayout(layout)
mdiArea = QMdiArea()
layout.addWidget(mdiArea)
newItem1 = vrayRectWidget(mdiArea, 'aaaa')
newItem2 = vrayRectWidget(mdiArea, 'bbbb')
newItem1.setMouseTracking(True)
newItem2.setMouseTracking(True)
#----------------------------------------------------------------------
#----------------------------------------------------------------------
class vrayRectWidget(QMdiSubWindow):
def __init__(self, parent, name):
super(vrayRectWidget, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowTitle(name)
self.view = MyView()
self.view.setMouseTracking(True)
self.setWidget(self.view)
#----------------------------------------------------------------------
#----------------------------------------------------------------------
class MyView(QGraphicsView):
def __init__(self):
QGraphicsView.__init__(self)
self.setGeometry(QRect(100, 100, 600, 400))
self.setDragMode(QGraphicsView.RubberBandDrag)
self.setRubberBandSelectionMode(Qt.IntersectsItemShape)
self.setMouseTracking(True)
self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
self.scene = QGraphicsScene(self)
self.scene.setSceneRect(QRectF())
self.setScene(self.scene)
self.setInteractive(True)
for i in range(5):
item = QGraphicsEllipseItem(i*75, 10, 60, 40)
item.setFlag(QGraphicsItem.ItemIsMovable, True)
item.setFlag(QGraphicsItem.ItemIsSelectable, True)
self.scene.addItem(item)
def mousePressEvent(self, event):
print('mousePressEvent')
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# window
def cacheWnd():
wnd = MainForm()
wnd.show()
cacheWnd()

you need an eventfilter in MyView:
def eventFilter(self,obj,event):
if obj == self and event.type() == QtCore.QEvent.MouseButtonPress: # see http://doc.qt.io/qt-5/qevent.html
# alternatively use QtCore.QEvent.GraphicsSceneMousePress
print('mousePressEvent')
return True
return QtWidgets.QGraphicsView.eventFilter(self,obj,event)
and install him at the end of the constructor of MyView:
self.installEventFilter(self)

problem with mouse events resolved.
I started a new class based on QGraphicsScene, and it handles all mouse events:
class GraphicsScene(QGraphicsScene):
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(parent)
self.parent = parent
def mouseReleaseEvent(self, event):
print('mouseReleaseEvent')
return QGraphicsScene.mouseReleaseEvent(self, event)

Related

Is there an elegant way to delete a widget in QScrollArea in PyQt?

I am learning PyQt5 recently and have problems when I want to delete a widget in QScrollArea. Is there an elegant way to visit the element in QScrollArea and delete it when the "delete" button in that element is clicked? Thank you for any help!
class MyWidget(QWidget):
def __init__(self, id):
super().__init__()
self.layout = QHBoxLayout()
self.layout.addWidget(QPlainTextEdit(id))
self.layout.addWidget(QPushButton('Delete'))
self.setLayout(self.layout)
# connect options
connect_options()
def connect_options(self):
pass
class MyList(QScrollArea):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.widget = QWidget()
for x in range(10):
self.layout.addWidget(MyWidget(str(x)))
self.widget.setLayout(self.layout)
self.setMinimumSize(1024, 500)
self.setWidget(self.widget)
First I would suggest you look into QListWidget, It will provide you various functions to handle situations like this.
for your problem, you will need to delete the widget from its parent layout & then delete it from the GUI
class MyWidget(QWidget):
def __init__(self, id):
super().__init__()
self.layout = QHBoxLayout()
self.layout.addWidget(QPlainTextEdit(id))
del_btn = QPushButton('Delete')
del_btn.clicked.connect(self._delete) # connect the click event to your delete function
self.layout.addWidget(del_btn)
self.setLayout(self.layout)
# connect options
self.connect_options()
def connect_options(self):
pass
def _delete(self):
# here you will delete your widget
parent_layout = self.parent().layout()
parent_layout.removeWidget(self) # remove the widget from its parent layout
self.deleteLater() # lets Qt knows it needs to delete this widget from the GUI
del self

Open another wx.python frame cause the main UI to be blocked

I am trying to open another wx frame from a main frame UI. I followed the example shown here: https://wiki.wxpython.org/CallAfter but my main UI is still blocked.
Here is the event listener on the main UI:
def testShowGUI(self):
# This process is a long one
# It uses the vtk to read point cloud file and reconstruct the surface
file = "cache/d9c5e8ef-7b7f-485e-8fc8-23098c32afcb.ply"
reader = vtk.vtkPLYReader()
reader.SetFileName(file)
reader.Update()
delaunay = vtk.vtkDelaunay2D()
delaunay.SetAlpha(0.1)
delaunay.SetTolerance(0.0001)
delaunay.SetOffset(1.25)
delaunay.BoundingTriangulationOff()
delaunay.SetInputData(reader.GetOutput())
delaunay.Update()
#Once finish reading and processing the point cloud, pass to the next function for rendering
wx.CallAfter(self.AfterProcess, delaunay)
def AfterProcess(self, data):
meshVisGui = MesVisGUI.MeshVisGui(data)
meshVisGui.Show()
def OnEnter(self, event):
#Event listener when user click on Enter button
my_thread = threading.Thread(target=self.testShowGUI)
my_thread.start()
The code for the separate frame is as below:
class MeshVisGui(wx.Frame):
SPACING = 4
def __init__(self, delaunay, parent=None):
self.delaunayData = delaunay
self.title = "Mesh Visualization"
wx.Frame.__init__(self, None, -1, self.title)
self.Initialize()
def Initialize(self):
self.panel = wx.Panel(self, -1, size=(600, 400), style=wx.BORDER_RAISED)
self.widget3d = wxVTKRenderWindowInteractor(self.panel, -1)
self.widget3d.Enable()
self.render = vtk.vtkRenderer()
self.render.SetBackground(params.BackgroundColor)
self.widget3d.GetRenderWindow().AddRenderer(self.render)
self.interactor = self.widget3d.GetRenderWindow().GetInteractor()
self.interactor.GetInteractorStyle().SetCurrentStyleToTrackballCamera()
self.axesWidget = utils.createAxes(self.interactor)
self.meshActor = utils.build_actor(self.delaunayData)
self.render.AddActor(self.meshActor)
self.render.ResetCamera()
box = wx.BoxSizer(wx.VERTICAL)
box.Add(self.widget3d, 1, wx.EXPAND, self.SPACING)
self.panel.SetSizer(box)
self.Layout()
However, on my main UI, it is still showing a spinning icon and block the UI while it is trying to process the point cloud data. Can someone help me spot what I have done wrong?
Wxpython version: 4.0.1
Python version: 3.6.5
Following on from Mike Driscoll's answer and comment, here is a sample of a threaded task running from another panel.
The thread reports back to the 2nd panel (it's parent) using an event. This allows a progress bar to be updated.
The 2nd panel includes a "Cancel" option for the threaded task, whilst the main frame has a button to test if it is not frozen.
The use of wx.GetApp().Yield() may be slightly old fashioned but I have always found it to be reliable.
import time
import wx
from threading import Thread
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class ThreadFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
panel = wx.Panel(self)
self.btn = wx.Button(panel,label='Stop Long running process', size=(200,30), pos=(10,10))
self.btn.Bind(wx.EVT_BUTTON, self.OnExit)
self.progress = wx.Gauge(panel,size=(300,10), pos=(10,50), range=300)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
#Bind to Exit on frame close
self.Bind(wx.EVT_CLOSE, self.OnExit)
self.Show()
self.mythread = TestThread(self)
#Enable the GUI to be responsive by briefly returning control to the main App
while self.mythread.isAlive():
time.sleep(0.1)
wx.GetApp().Yield()
continue
try:
self.OnExit(None)
except:
pass
def OnProgress(self, event):
self.progress.SetValue(event.count)
#or for indeterminate progress
#self.progress.Pulse()
def OnExit(self, event):
if self.mythread.isAlive():
self.mythread.terminate() # Shutdown the thread
self.mythread.join() # Wait for it to finish
self.Destroy()
class TestThread(Thread):
def __init__(self,parent_target):
Thread.__init__(self)
self.parent = parent_target
self.stopthread = False
self.time = time.time()
self.start() # start the thread
def run(self):
# A loop that will run for 5 minutes then terminate
while self.stopthread == False:
curr_loop = int(time.time() - self.time)
if curr_loop < 300:
time.sleep(1)
evt = progress_event(count=curr_loop)
#Send back current count for the progress bar
try:
wx.PostEvent(self.parent, evt)
except: # The parent frame has probably been destroyed
self.terminate()
else:
self.terminate()
def terminate(self):
self.stopthread = True
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.text_count = 0
self.parent=parent
btn = wx.Button(self, wx.ID_ANY, label='Start Long running process', size=(200,30), pos=(10,10))
btn.Bind(wx.EVT_BUTTON, self.Thread_Frame)
btn2 = wx.Button(self, wx.ID_ANY, label='Is the GUI still active?', size=(200,30), pos=(10,50))
btn2.Bind(wx.EVT_BUTTON, self.AddText)
self.txt = wx.TextCtrl(self, wx.ID_ANY, style= wx.TE_MULTILINE, pos=(10,90),size=(400,100))
def Thread_Frame(self, event):
frame = ThreadFrame(title='Threaded Task', parent=self.parent)
def AddText(self,event):
self.text_count += 1
txt = self.txt.GetValue()
txt += "More text " + str(self.text_count)+"\n"
self.txt.SetValue(txt)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame', size=(600,400))
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
You do not need threads to open new frames / windows in your wxPython application. You just need to create a sub-class of wx.Frame to hold the code of your other frame. Then from your main application's frame, you can instantiate the other frame and show it. You use the same concept when you create a wx.Dialog or a wx.MessageDialog.
Here is a simple example:
import wx
class OtherFrame(wx.Frame):
"""
Class used for creating frames other than the main one
"""
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title)
self.Show()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
btn = wx.Button(self, label='Create New Frame')
btn.Bind(wx.EVT_BUTTON, self.on_new_frame)
def on_new_frame(self, event):
frame = OtherFrame(title='SubFrame',
parent=wx.GetTopLevelParent(self))
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Main Frame')
panel = MyPanel(self)
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = MainFrame()
app.MainLoop()
In this example, I set the other frame's parent to the MainFrame instance by using wx.GetTopLevelParent(self). The benefit of setting a parent for the sub-frames is that if I close the main frame, it will cause the other frames to also be closed.

Qt pass focus between windows

In the following example I have a Qt button which opens a pop-out window. However, the pop-out window takes the focus entirely. Is it possible to modify this such that I can still interact with the main window by moving the mouse over it, e.g. to change the value of QDoubleSpinBox even when the pop-out window is open? I think I may need to use the QtHoverEvent class put I can't find a good example of how to do this.
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QGridLayout, QDoubleSpinBox,\
QLabel
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
btn = QPushButton('Button', self)
btn.move(50, 50)
btn2 = QDoubleSpinBox(self)
btn2.move(50,100)
self.sideWindowTest = sideWindowTest(self)
btn.clicked.connect(lambda: self.sideWindowTest.setupWindow())
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Main')
self.show()
class sideWindowTest(object):
myWdg = None
def __init__(self, parent):
super().__init__()
self.viewer = parent
def initiateMenuBar(self):
self.myWdg.setWindowTitle('Phasing')
self.myWdg.setWindowModality(Qt.ApplicationModal)
MenuBar = QVBoxLayout()
self.labels = {
'phase 0': QLabel('Phase 0', self.myWdg),
}
self.inputs = {
'phase 0': QDoubleSpinBox(self.myWdg),
}
for i in self.inputs.values():
i.installEventFilter(self.myWdg)
self.inputs['phase 0'].setValue(0)
MenuBar.addWidget(self.labels['phase 0'])
MenuBar.addWidget(self.inputs['phase 0'])
MenuBar.addStretch(1)
return MenuBar
def setupWindow(self):
if not self.myWdg:
self.myWdg = QWidget()
MenuBar = self.initiateMenuBar()
grid = QGridLayout()
grid.setSpacing(10)
grid.addLayout(MenuBar, 0, 0, 1, 2)
self.myWdg.setLayout(grid)
self.myWdg.setGeometry(0, 0, 400, 100)
self.myWdg.show()
self.myWdg.activateWindow()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
Yes, but you have to explicitly wrap your QWidget's in a QDialog or a QMainWindow. By default, if you create a orphan QWidget (as you are doing), Qt will wrap it in a QDialog.
Just change your class type to QDialog. Also, you should probably pass in the initial main window as the parent.
btn.clicked.connect(lambda: self.sideWindowTest.setupWindow(self))
...
def setupWindow(self, parent=None):
if not self.myWdg:
self.myWdg = QDialog(parent)

PyQt keyPressEvent not triggered from QDialog

I have a simple example of of a dialog window that has the keyPressEvent method. However, no matter what is typed when the sub window has focus, the event is not triggered.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import PyQt4.Qt
class KpeWindow(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
main = QVBoxLayout(self)
label = QLabel(self)
label.setText('Test the keyPressEvent')
self.adjustSize()
self.setLayout(main)
def keyPressEvent(self, event):
QMessageBox.warning(self, 'MDI', 'keyPressEvent')
super().keyPressEvent(event)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('KeyPressEvent Test')
child = KpeWindow()
self.setCentralWidget(child)
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit(app.exec_())
The following code works:
class KpeWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self,parent)
main = QVBoxLayout(self)
label = QLabel(self)
label.setText('Test the keyPressEvent')
main.addWidget(label)
self.adjustSize()
self.setLayout(main)
def keyPressEvent(self, event):
QMessageBox.warning(self, 'MDI', 'keyPressEvent')
self.parent().keyPressEvent(event)
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowTitle('KeyPressEvent Test')
main = QVBoxLayout(self)
child = KpeWindow(self)
child.setFocusPolicy(Qt.StrongFocus)
self.setFocusProxy(child)
main.addWidget(child)
child.setFocus(True)
self.adjustSize()
self.setLayout(main)
I am not sure which of my changes work, I suspect setFocusProxy. In general I would recommend using QWidget as the child, and putting things into layouts even when there are no siblings.
The keyPressEvent is sensitive to the focus policy. In your example, the event is going to the QMainWindow (if you move the keyPressEvent to there, it does receive key events).
Is there any reason to have a dialog within a window? If you launch the dialog in the usual way, using child.show(), child.exec_() instead of setCentralWidget, it shows in a separate window and captures the key event.

PyQt4 Mdi sub window not resizing properly

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_())

Resources