This question already has an answer here:
pyqt QTableWidgetItem connect signal
(1 answer)
Closed 3 years ago.
I am trying to retrieve the editted data from a pyqt5 qtablewidget and am struggling to understand how to do this. My simple code is shown below:
class Delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
DBL_MAX = 1.7976931348623157e308
editor = QDoubleSpinBox(parent, minimum=-DBL_MAX, maximum=DBL_MAX, decimals=323)
return editor
class TableView(QTableWidget):
def __init__(self, z, *args):
super(TableView, self).__init__(*args)
self.z = z
self.setz()
self.resizeColumnsToContents()
self.resizeRowsToContents()
delegate = Delegate(self)
self.setItemDelegate(delegate)
def setz(self):
horHeaders = []
for j, (key, values) in enumerate(sorted(self.z.items())):
horHeaders.append(key)
for i, value in enumerate(values):
newitem = QTableWidgetItem()
newitem.setData(Qt.EditRole, value)
self.setItem(i, j, newitem)
self.setHorizontalHeaderLabels(horHeaders)
def slot(self):
row= self.tableWidget.currentItem()
print(str(row))
def main(args):
z = {
"Let's Sum This Row 1": [0, 0],
"Let's Sum This Row 2": [0, 0],
}
app = QApplication(args)
table = TableView(z, 2, 2)
table.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
a = z
sum1=a[0][0]+a[0][1]
sum2=a[1][0]+a[1][1]
print(sum1)
print(sum2)
In my simple example my initial z data is a zeros. Let's say that the user changes the first rows to 1's and the second row to 2's.
The end output I want is sum1 = 2 and sum2 = 4.
How do i tell my program to read the end user editted matrix and store that back to a variable so that I can do more work further on in my program?
From my research, it looks like itemchanged may do the trick, but I am struggling to understand how to implement this in my code.
edited your code to add itemChanged and calculate
from PyQt5.QtWidgets import (QStyledItemDelegate, QDoubleSpinBox, QTableWidget, QTableWidgetItem, QApplication)
from PyQt5.QtCore import (Qt, QSize)
import sys
class Delegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
DBL_MAX = 1.7976931348623157e308
editor = QDoubleSpinBox(parent, minimum=-DBL_MAX, maximum=DBL_MAX, decimals=323)
return editor
class TableView(QTableWidget):
def __init__(self, z, *args):
super(TableView, self).__init__(*args)
self.setMinimumSize(QSize(800, 600))
self.z = z
self.setz()
self.resizeColumnsToContents()
self.resizeRowsToContents()
delegate = Delegate(self)
self.setItemDelegate(delegate)
def setz(self):
horHeaders = []
for j, (key, values) in enumerate(sorted(self.z.items())):
horHeaders.append(key)
for i, value in enumerate(values):
newitem = QTableWidgetItem()
newitem.setData(Qt.EditRole, value)
newitem
self.setItem(i, j, newitem)
self.setHorizontalHeaderLabels(horHeaders)
def slot(self):
row = self.tableWidget.currentItem()
print(str(row))
def main(args):
z = {
"Let's Sum This Row 1": [0, 0],
"Let's Sum This Row 2": [0, 0],
}
app = QApplication(args)
table = TableView(z, 2, 2)
def _calculate():
for j in range(table.columnCount()):
counter = 0
for i in range(table.rowCount()):
counter = counter + int(table.item(i, j).data(0))
print(f"col{j}: sum={counter}")
table.itemChanged.connect(_calculate)
table.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
Related
I'm having couple of issues while trying to make a QHeaderView for my QTableView.
I want QHeaderView to be resizable by the user (Qt.ResizeMode.Interactive) while being able to stretch its sections proportionately when the window or QTableView is being resized. I found this problem online, and managed to mostly solve it but there is still some stuttering when the resizing begins and I think there should be a better solution than mine. Currently it's done by using QTimer to stop sections from going out of the viewport. Timer is being updated every millisecond. If the update interval is bigger, sections would go out of viewport and magically teleport back when the timer is updated, so once per millisecond in my case. There's still some stuttering visible if the user is dragging sections out of the viewport by dragging their mouse faster, not so visible when the mouse is slower, but visible none the less.
Every section should be resizable and movable, besides the first two. The first two sections should be immovable and fixed. I managed to make them fixed and they don't seem to have an effect on resizing of the sections, but I have no idea how to make them immovable while all the other sections are movable.
Sections should have text eliding, which I managed to make an item delegate for, but setting it on QHeaderView seems to do absolutely nothing (paint() method doesn't even get called). It's probably because item delegate isn't affecting sections, if so, how can I make a delegate that does affect them?
Here's my current code (it's a bit of a mess, but hopefully you'll get the idea):
import sys
import weakref
from typing import Any, Optional
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import pyqtSlot, Qt
from PyQt6.QtGui import QFontMetrics
from PyQt6.QtWidgets import QHeaderView, QStyledItemDelegate, QStyleOptionViewItem
class MyItemDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
text = index.data(Qt.ItemDataRole.DisplayRole)
# print(text)
if text:
elided_text = QFontMetrics(option.font).elidedText(str(text), Qt.TextElideMode.ElideRight, option.rect.width())
painter.drawText(option.rect, Qt.AlignmentFlag.AlignLeft, elided_text)
class HeaderView(QtWidgets.QHeaderView):
def __init__(self,
orientation: QtCore.Qt.Orientation = Qt.Orientation.Horizontal,
parent: Optional[QtWidgets.QWidget] = None):
super(HeaderView, self).__init__(orientation, parent)
item_delegate = MyItemDelegate(self)
self.setItemDelegate(item_delegate)
self.setMinimumSectionSize(5)
self.setStretchLastSection(True)
self.setCascadingSectionResizes(True)
self.setSectionsMovable(True)
self.fixed_section_indexes = (0, 1)
timer = QtCore.QTimer(self)
timer.setSingleShot(True)
timer.setTimerType(Qt.TimerType.PreciseTimer)
timer.timeout.connect(self._update_sizes)
resize_mode_timer = QtCore.QTimer(self)
resize_mode_timer.setTimerType(Qt.TimerType.PreciseTimer)
resize_mode_timer.setSingleShot(True)
resize_mode_timer.timeout.connect(lambda: self.setSectionResizeMode(QHeaderView.ResizeMode.Interactive))
self._resize_mode_timer = weakref.proxy(resize_mode_timer)
self._timer = weakref.proxy(timer)
self.sectionResized.connect(self._handle_resize)
self.setTextElideMode(Qt.TextElideMode.ElideLeft)
self.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
self.proportions = []
self.mouse_pressed = False
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None:
self.mouse_pressed = False
super().mouseReleaseEvent(e)
self.proportions = [self.sectionSize(i) / self.width() for i in range(self.count())]
# print(self.mouse_pressed)
def init_sizes(self):
each = self.width() // self.count()
for i in range(self.count()):
self.resizeSection(self.logicalIndex(i), each)
#pyqtSlot(int, int, int)
def _handle_resize(self, logicalIndex: int, oldSize: int, newSize: int):
self._timer.start(1)
def resizeEvent(self, event: QtGui.QResizeEvent):
super().resizeEvent(event)
width = self.width()
# sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
for i in range(self.count()):
if not self.proportions:
break
if i not in self.fixed_section_indexes:
self.resizeSection(i, int(self.proportions[i] * width_without_fixed))
self._timer.start(1)
#pyqtSlot()
def _update_sizes(self):
width = self.width()
sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
# width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
index = len(sizes) - 1
i = 0
while index >= 0 and sum(sizes) > width:
i += 1
if i > 100:
break
if sizes[index] > 5 and index not in self.fixed_section_indexes: # minimum width (5)
new_width = width - (sum(sizes) - sizes[index])
if new_width < 5:
new_width = 5
sizes[index] = new_width
index -= 1
for j, value in enumerate(sizes):
self.resizeSection(self.logicalIndex(j), value)
if not self.proportions:
self.proportions = [self.sectionSize(i) / width for i in range(self.count())]
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super(Model, self).__init__(parent)
self.__headers = ["Column A", "Column B", "Column C", "Column D", "Column E", "Column F", "Column G"]
self.__data = []
for i in range(10):
row = [0, 1, 2, 3, 42222222222, 5, 6, 74444444]
self.__data.append(row)
def rowCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__data)
def columnCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__headers)
def headerData(self, section: int, orientation: QtCore.Qt.Orientation,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal:
return self.__headers[section]
return f"{section}"
return None
def data(self, index: QtCore.QModelIndex,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]:
return self.__data[index.row()][index.column()]
return None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = QtWidgets.QTableView()
view.resize(600, 600)
header = HeaderView()
view.setHorizontalHeader(header)
model = Model()
view.setModel(model)
header.init_sizes()
view.horizontalHeader().resizeSection(0, 30)
view.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
view.horizontalHeader().resizeSection(1, 30)
view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
view.show()
app.exec()
I've got a grid of analysis results that shows up in my results frame, and after creating the grid object I call grid.AutoSizeColumns() to fit the columns to the data contents. This works fine for the actual columns containing data, but the leftmost column (the 'index' column?) containing just the row indices remains fairly wide -- much wider than needed, since there's only four rows.
What I'd like to do:
-Remove the leftmost column (the gray one containing only row indices), and/or
-Remove the actual "Index" column (with the header "Index") as it's redundant, and narrow the leftmost gray column, OR
-Remove both the leftmost column AND the column labeled "index".
Note: I am aware of the grid.DeleteCols(pos=0, numCols=1) method, but this always throws an error:
wx._core.wxAssertionError: C++ assertion ""Assert failure"" failed at ..\..\src\generic\grid.cpp(1471) in wxGridTableBase::DeleteCols(): Called grid table class function DeleteCols
but your derived table class does not override this function
and I'm not sure how to "override this function". If someone could explain the error to me/how to fix it so that grid.DeleteCols() works, that would be halfway to solving my issue (the other half being narrowing or deleting that gray far-left column in the grid)
Minimal code example below, along with a screenshot of what it currently looks like (taken from the actual program, not the minimal code)
On a related note, is there an easy way to right-justify the cell values in the grid? I format the results to all have the same number of decimal places, and I think it would look neater if the values were right-justfied (or even centered).
Minimal example:
import wx
import wx.grid
import numpy as np
import pandas as pd
class StartFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="title", size=wx.Size(900,600),
style=wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.RESIZE_BORDER)
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
self.numarray=[[1,0.0000,0.071,3.535,3.313,0.22],
[10,1.000,0.071,3.535,3.888,-0.353],
[20,1.301,0.357,4.634,4.526,0.108],
[50,1.699,0.929,6.456,6.442,0.023]]
self.df = pd.DataFrame(self.numarray, columns=['Parasites/ml','log_qty',
'probability','probit','est. probit','residual'])
self.table = DataTable(self.df)
grid = wx.grid.Grid(self, -1)
grid.SetTable(self.table, takeOwnership=True)
grid.AutoSizeColumns()
self.pnl = wx.Panel(self)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
self.hsizer.AddStretchSpacer()
self.hsizer.Add(grid, 0, wx.CENTER)
self.hsizer.AddStretchSpacer()
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.hsizer, wx.SizerFlags().Expand().Border(wx.ALL, 5))
self.vsizer.AddStretchSpacer()
self.pnl.SetSizerAndFit(self.vsizer)
class DataTable(wx.grid.GridTableBase):
def __init__(self, printdata=None):
wx.grid.GridTableBase.__init__(self)
self.headerRows = 1
if printdata is None: data = pd.DataFrame()
self.data = printdata
def GetNumberRows(self):
return len(self.data)
def GetNumberCols(self):
return len(self.data.columns) + 1
def GetValue(self, row, col):
if col == 0: return self.data.index[row]
return self.data.iloc[row, col - 1]
def SetValue(self, row, col, value):
self.data.iloc[row, col - 1] = value
def GetColLabelValue(self, col):
if col == 0:
if self.data.index.name is None: return 'Index'
else: return self.data.index.name
return str(self.data.columns[col - 1])
def main():
app = wx.App()
frm = StartFrame(None)
frm.Show()
app.MainLoop()
if __name__ == "__main__":
main()
You can Hide Columns and/or Rows, so that is an easy method of dealing with the errant left-hand column.
Hide comes in useful for other things as well e.g. grid.HideRowLabels(), will hide those pesky row numbers of the left.
The alignment issue could probably be handled by using a wx.grid.GridCellAttrProvider or you can simply iterate over the grid setting each cell, as I have done below for simplicity.
import wx
import wx.grid
import numpy as np
import pandas as pd
class StartFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent=parent,
id=wx.ID_ANY, title="title", size=wx.Size(900,600),
style=wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.SYSTEM_MENU|wx.RESIZE_BORDER)
self.SetSizeHints(wx.DefaultSize, wx.DefaultSize)
self.numarray=[[1,0.0000,0.071,3.535,3.313,0.22],
[10,1.000,0.071,3.535,3.888,-0.353],
[20,1.301,0.357,4.634,4.526,0.108],
[50,1.699,0.929,6.456,6.442,0.023]]
self.df = pd.DataFrame(self.numarray, columns=['Parasites/ml','log_qty',
'probability','probit','est. probit','residual'])
self.table = DataTable(self.df)
grid = wx.grid.Grid(self, -1)
grid.SetTable(self.table, takeOwnership=True)
# Hide Index column
grid.HideCol(0)
grid.HideRowLabels()
# Alignment of cells
r = grid.GetNumberRows()
c = grid.GetNumberCols()
for row in range(r):
for cell in range(c):
grid.SetCellAlignment(row, cell, wx.ALIGN_RIGHT, wx.ALIGN_CENTER)
grid.AutoSizeColumns()
self.pnl = wx.Panel(self)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
self.hsizer.AddStretchSpacer()
self.hsizer.Add(grid, 0, wx.CENTER)
self.hsizer.AddStretchSpacer()
self.vsizer.AddStretchSpacer()
self.vsizer.Add(self.hsizer, wx.SizerFlags().Expand().Border(wx.ALL, 5))
self.vsizer.AddStretchSpacer()
self.pnl.SetSizerAndFit(self.vsizer)
class DataTable(wx.grid.GridTableBase):
def __init__(self, printdata=None):
wx.grid.GridTableBase.__init__(self)
self.headerRows = 1
if printdata is None: data = pd.DataFrame()
self.data = printdata
def GetNumberRows(self):
return len(self.data)
def GetNumberCols(self):
return len(self.data.columns) + 1
def GetValue(self, row, col):
if col == 0: return self.data.index[row]
return self.data.iloc[row, col - 1]
def SetValue(self, row, col, value):
self.data.iloc[row, col - 1] = value
def GetColLabelValue(self, col):
if col == 0:
if self.data.index.name is None: return 'Index'
else: return self.data.index.name
return str(self.data.columns[col - 1])
def main():
app = wx.App()
frm = StartFrame(None)
frm.Show()
app.MainLoop()
if __name__ == "__main__":
main()
I'm working with a listWidget in PyQT5. I would like to have a hidden value associated with each item in the list that I can extract when the list text is clicked.
class Gui(QtWidgets.QMainWindow, ui_tutorial.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.exiting = True
self.setupUi(self)
self.fill_list()
self.listWidget_emails.itemClicked.connect(self.extract)
def fill_list()
w = self.listWidget
w.clear()
titles = [['title_1', 1], ['title_2', 2], 'title_3', 3]]
for t in titles:
title = t[0]
value = t[1]
w.addItem(title, value) #this does not work
def extract(self, list_item):
w = self.listWidget
myValue = list_item.value() #This is just to describe what I want. Of course it doesn't work
def main():
app = QtWidgets.QApplication(sys.argv)
form = Gui()
form.show() # Show the form
app.exec_() # start the app
if __name__ == '__main__':
main()
You could construct a QListWidgetItem for each title and assign your own value property to the object.
def fill_list(self):
w = self.listWidget
w.clear()
titles = [['title_1', 1], ['title_2', 2], ['title_3', 3]]
for t in titles:
item = QtWidgets.QListWidgetItem(t[0])
item.value = t[1]
w.addItem(item)
def extract(self, list_item):
myValue = list_item.value
I have below code snippet from the help of stackoverflow followers.
I am able to filter the table now. but when i try to filter it sorts first as i enabled sort for the view.
I want to create the QTableview such a way that if i click on header it should sort. and should have a dropdown box (may be combox box style) at the right of every header. i am uploading a snap of how i want (which i made it in .NET)
Code Snippet
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtCore, QtGui
class myWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(myWindow, self).__init__(parent)
self.centralwidget = QtGui.QWidget(self)
self.view = QtGui.QTableView(self.centralwidget)
self.view.setSortingEnabled(True)
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
self.setCentralWidget(self.centralwidget)
self.model = QtGui.QStandardItemModel(self)
for rowName in range(3) * 5:
self.model.invisibleRootItem().appendRow(
[ QtGui.QStandardItem("row {0} col {1}".format(rowName, column))
for column in range(3)
]
)
self.proxy = QtGui.QSortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.view.setModel(self.proxy)
self.horizontalHeader = self.view.horizontalHeader()
self.horizontalHeader.sectionClicked.connect(self.horizontalHeader_Clicked)
#QtCore.pyqtSlot(int)
def horizontalHeader_Clicked(self, logicalIndex):
self.logicalIndex = logicalIndex
# local variable, and no parent
menuValues = QtGui.QMenu()
# delete the previous one
try:
self.signalMapper.deleteLater()
except:
pass
self.signalMapper = QtCore.QSignalMapper(self)
valuesUnique = [
self.proxy.index(row, self.logicalIndex).data().toString()
for row in xrange(self.proxy.rowCount())
]
print 'printing col %d values' % self.logicalIndex
for row in range(self.proxy.rowCount()):
print 'row %d Item %s' % (row,self.model.item(row, self.logicalIndex).text())
actionAll = QtGui.QAction("All", self)
actionAll.triggered.connect(self.actionAll)
menuValues.addAction(actionAll)
menuValues.addSeparator()
for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):
action = QtGui.QAction(actionName, self)
self.signalMapper.setMapping(action, actionNumber)
action.triggered.connect(self.signalMapper.map)
menuValues.addAction(action)
self.signalMapper.mapped.connect(self.signalMapper_mapped)
headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
posY = headerPos.y() + self.horizontalHeader.height()
posX = headerPos.x() + self.horizontalHeader.sectionPosition(self.logicalIndex)
menuValues.exec_(QtCore.QPoint(posX, posY))
#QtCore.pyqtSlot()
def actionAll(self):
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( "",
QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.RegExp
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
#QtCore.pyqtSlot(int)
def signalMapper_mapped(self, i):
stringAction = self.signalMapper.mapping(i).text()
filterColumn = self.logicalIndex
filterString = QtCore.QRegExp( stringAction,
QtCore.Qt.CaseSensitive,
QtCore.QRegExp.FixedString
)
self.proxy.setFilterRegExp(filterString)
self.proxy.setFilterKeyColumn(filterColumn)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
main = myWindow()
main.show()
main.resize(400, 600)
sys.exit(app.exec_())
This i how i am trying to get (Sort and filter)
If possible I need the ability to set the filter for selected columns only like in above image.
There is a discussion location here about the same topic: Quick way for QWidget in QHeaderView's columns?
They suggest that you would need to ditch the stock QHeaderView in your view, and provide your own custom widget for the header functionality, in order to place custom widgets into the header sections.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I'm using Pyside to create an interactive subclassed QGraphicsView - containing some subclassed QGraphicsItems.
It works well in its own module and receives mouse events.
But when used as a module from another file, and incorporated into another Layout - my mouse events are not being triggered.
However .itemChange is working.
Everything except the mouse events.
I am not using tracking events such as hover. I am using mousePressEvent and mouseReleaseEvent.
I have seen c++ responses about setting "setMouseTracking" but this is for widgets and my QGraphicsItems have been added as items not widgets. So when I call this funcion it tells me it doesn't exist for items. Also that seems to be for hover type of events - which I am not needing.
I believe I am subclassing properly and I pass on the events to the parent class. As I said at the start - my code works fine in a standalone file.
Any ideas what I've forgotten to do ?
Here is the standalone - working program:
Save as test_subclass_module.py
import sys
import weakref
import math
from PySide import QtCore, QtGui
###
class Edge(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 2
def __init__(self, sourceNode, destNode):
QtGui.QGraphicsItem.__init__(self)
#
self.sourcePoint = QtCore.QPointF()
self.destPoint = QtCore.QPointF()
self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.source = weakref.ref(sourceNode)
self.dest = weakref.ref(destNode)
self.source().addEdge(self)
self.dest().addEdge(self)
self.set_index()
self.adjust()
def type(self):
return Edge.Type
def sourceNode(self):
return self.source()
def setSourceNode(self, node):
self.source = weakref.ref(node)
self.adjust()
def destNode(self):
return self.dest()
def setDestNode(self, node):
self.dest = weakref.ref(node)
self.adjust()
def set_index(self):
self.setToolTip(self.source().label)
def adjust(self):
# do we have a line to draw ?
if self.source() and self.dest():
line = QtCore.QLineF(self.mapFromItem(self.source(), 0, 0), self.mapFromItem(self.dest(), 0, 0))
length = line.length()
if length > 20:
edgeOffset = QtCore.QPointF((line.dx() * 10) / length, (line.dy() * 10) / length)
self.prepareGeometryChange()
self.sourcePoint = line.p1() + edgeOffset
self.destPoint = line.p2() - edgeOffset
else: # want to make sure line not drawn
self.prepareGeometryChange()
self.sourcePoint = self.destPoint
def boundingRect(self):
# do we have a line to draw ?
if not self.source() or not self.dest():
return QtCore.QRectF()
else:
extra = 1
return QtCore.QRectF(self.sourcePoint,
QtCore.QSizeF(self.destPoint.x() - self.sourcePoint.x(),
self.destPoint.y() - self.sourcePoint.y())).normalized().adjusted(-extra, -extra, extra, extra)
def paint(self, painter, option, widget):
if self.source() and self.dest():
# Draw the line itself.
line = QtCore.QLineF(self.sourcePoint, self.destPoint)
if line.length() > 0.0:
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawLine(line)
###
class Node(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 1
def __init__(self, graphWidget, time, temp, pos):
QtGui.QGraphicsItem.__init__(self)
self.graph = weakref.ref(graphWidget)
self.edgeList = []
self.set_index(pos)
self.newPos = QtCore.QPointF()
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
self.setFlag(QtGui.QGraphicsItem.ItemSendsGeometryChanges)
self.setCacheMode(self.DeviceCoordinateCache)
self.setZValue(-1)
#
self.temp = temp
self.time = time
x,y = self.map_temptime_to_pos()
self.setPos(x,y)
self.marker = False
def type(self):
return Node.Type
def addEdge(self, edge):
self.edgeList.append(weakref.ref(edge))
def set_index(self, index):
self.index = index
self.label = "Step %d" % index
self.setToolTip(self.label)
def get_prev_edge(self):
index = 1000
edge = False
for e in self.edgeList:
sn = e().source().index
dn = e().dest().index
if sn < index:
index = sn
edge = e
if dn < index:
index = dn
edge = e
return edge
def get_next_edge(self):
index = -1
edge = False
for e in self.edgeList:
sn = e().source().index
dn = e().dest().index
if sn > index:
index = sn
edge = e
if dn > index:
index = dn
edge = e
return edge
def map_temptime_to_pos(self):
x = self.time * self.graph().graph_width_ratio
y = self.graph().size[3] - self.temp * self.graph().graph_height_ratio
return (x,y)
def boundingRect(self):
adjust = 2.0
return QtCore.QRectF(-10 - adjust, -10 - adjust,
22 + adjust, 23 + adjust)
def paint(self, painter, option, widget):
painter.drawLine(QtCore.QLineF(6,-40,6,-2))
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtCore.Qt.lightGray)
painter.drawEllipse(-10, -10, 20, 20)
gradient = QtGui.QRadialGradient(0, 0, 22)
if option.state & QtGui.QStyle.State_Sunken: # selected
gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.darkGreen).lighter(120))
else:
gradient.setColorAt(1, QtCore.Qt.blue)
painter.setBrush(QtGui.QBrush(gradient))
painter.setPen(QtGui.QPen(QtCore.Qt.black, 0))
painter.drawEllipse(-6, -6, 12, 12)
def itemChange(self, change, value):
if change == QtGui.QGraphicsItem.ItemPositionChange:
for edge in self.edgeList:
edge().adjust()
return QtGui.QGraphicsItem.itemChange(self, change, value)
def mousePressEvent(self, event):
if not self.graph().inhibit_edit:
self.update()
print "Node pressed"
QtGui.QGraphicsItem.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
if not self.graph().inhibit_edit:
self.update()
print "Node released"
#
QtGui.QGraphicsItem.mouseReleaseEvent(self, event)
###
class GraphWidget(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.size = (-30, 30, 600, 400)
#
scene = QtGui.QGraphicsScene(self)
scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
scene.setSceneRect(self.size[0],self.size[1],self.size[2],self.size[3])
self.setScene(scene)
self.setCacheMode(QtGui.QGraphicsView.CacheBackground)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
#
self.maxtemp = 300
self.maxtime = 160
self.nodecount = 0
self.calc_upper_limits()
#
self.scale(0.8, 0.8)
self.setMinimumSize(600, 400)
self.setWindowTitle(self.tr("Elastic Nodes"))
self.inhibit_edit = False
def calc_upper_limits(self):
self.toptemp = (self.maxtemp / 100 + 1) * 100
self.toptime = (int(self.maxtime) / 30 + 1) * 30
self.graph_width_ratio = float(self.size[2]) /self.toptime
self.graph_height_ratio = float(self.size[3]) / self.toptemp
def add_node(self, time, temp, marker=False, pos=-1):
self.nodecount += 1
scene = self.scene()
# Insert Node into scene
node = Node(self, time, temp, self.nodecount)
scene.addItem(node)
# Insert new edges
nodes = self.get_ordered_nodes()
if len(nodes) > 1:
e = Edge(nodes[-2], node)
scene.addItem(e)
# cleanup edge tooltips
for n in self.get_ordered_nodes():
edges = n.edgeList
for e in edges:
e().set_index()
def get_ordered_nodes(self):
nodes = [item for item in self.scene().items() if isinstance(item, Node)]
nodes.sort(key=lambda n: n.index)
return nodes
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Plus:
self.scaleView(1.2)
elif key == QtCore.Qt.Key_Minus:
self.scaleView(1 / 1.2)
else:
QtGui.QGraphicsView.keyPressEvent(self, event)
def mousePressEvent(self, event):
print "GraphWidget mouse"
QtGui.QGraphicsView.mousePressEvent(self, event)
def wheelEvent(self, event):
self.scaleView(math.pow(2.0, -event.delta() / 240.0))
def scaleView(self, scaleFactor):
factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
if factor < 0.07 or factor > 100:
return
self.scale(scaleFactor, scaleFactor)
def drawBackground(self, painter, rect):
sceneRect = self.sceneRect()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
widget = GraphWidget()
widget.add_node(0, 25)
widget.add_node(30, 100)
widget.add_node(60, 200)
widget.show()
sys.exit(app.exec_())
Here is the parent program - which does not get the mouse events:
Called test_toplevel.py
# import user interface etc
from PySide import QtCore, QtGui
from test_tabs_ui import Ui_Form
from test_subclass_module import *
import sys
Programs = {"Gen13": {"steps": [[0, 0, 0], [0, 30, 30], [0, 60, 60], [0, 77, 77]]
}}
###-----------------------------------------------------------
### The dialog
class Nusku_tab_Add_kiln(QtGui.QWidget):
""" Create dialog to add/delete kilns from controlled kilns """
def __init__(self, parent=None):
# Get the UI loaded
super(Nusku_tab_Add_kiln, self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.current = Programs['Gen13']
# draw program in graphicsView
# swap out the standin
self.ui.graphLayout.removeWidget(self.ui.graphicsView)
self.ui.graphicsView.setParent(None)
self.ui.graphicsView.deleteLater()
self.graph = GraphWidget()
self.ui.graphLayout.addWidget(self.graph)
self.draw_graph()
def choose_program(self):
pass
def draw_graph(self):
graph = self.graph
graph.inhibit_edit = True
steps = self.current['steps']
for s in steps:
print s
graph.add_node(s[1],s[2])
###------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
prog = Nusku_tab_Add_kiln()
prog.show()
sys.exit(app.exec_())
For sake of completeness. Here is the ui file it imports:
test_tabs_ui.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test_tabs.ui'
#
# Created: Wed Dec 05 15:20:02 2012
# by: pyside-uic 0.2.14 running on PySide 1.1.1
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(595, 540)
self.verticalLayout = QtGui.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.tabWidget = QtGui.QTabWidget(Form)
self.tabWidget.setObjectName("tabWidget")
self.Tab_Program = QtGui.QWidget()
self.Tab_Program.setObjectName("Tab_Program")
self.verticalLayout_2 = QtGui.QVBoxLayout(self.Tab_Program)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.frame_graph_status = QtGui.QFrame(self.Tab_Program)
self.frame_graph_status.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_graph_status.setFrameShadow(QtGui.QFrame.Raised)
self.frame_graph_status.setObjectName("frame_graph_status")
self.horizontalLayout_7 = QtGui.QHBoxLayout(self.frame_graph_status)
self.horizontalLayout_7.setSpacing(0)
self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.frame_program = QtGui.QFrame(self.frame_graph_status)
self.frame_program.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_program.setFrameShadow(QtGui.QFrame.Raised)
self.frame_program.setObjectName("frame_program")
self.graphLayout = QtGui.QVBoxLayout(self.frame_program)
self.graphLayout.setContentsMargins(-1, 0, -1, 0)
self.graphLayout.setObjectName("graphLayout")
self.graphicsView = QtGui.QGraphicsView(self.frame_program)
self.graphicsView.setObjectName("graphicsView")
self.graphLayout.addWidget(self.graphicsView)
self.horizontalLayout_7.addWidget(self.frame_program)
self.verticalLayout_2.addWidget(self.frame_graph_status)
self.widget_prog = QtGui.QWidget(self.Tab_Program)
self.widget_prog.setObjectName("widget_prog")
self.prog_layout = QtGui.QGridLayout(self.widget_prog)
self.prog_layout.setContentsMargins(4, 4, 4, 4)
self.prog_layout.setSpacing(0)
self.prog_layout.setContentsMargins(0, 0, 0, 0)
self.prog_layout.setObjectName("prog_layout")
self.verticalLayout_2.addWidget(self.widget_prog)
self.tabWidget.addTab(self.Tab_Program, "")
self.Tab_alarms = QtGui.QWidget()
self.Tab_alarms.setObjectName("Tab_alarms")
self.alarms_tab_layout = QtGui.QVBoxLayout(self.Tab_alarms)
self.alarms_tab_layout.setObjectName("alarms_tab_layout")
self.tabWidget.addTab(self.Tab_alarms, "")
self.Tab_settings = QtGui.QWidget()
self.Tab_settings.setObjectName("Tab_settings")
self.settings_tab_layout = QtGui.QVBoxLayout(self.Tab_settings)
self.settings_tab_layout.setObjectName("settings_tab_layout")
self.tabWidget.addTab(self.Tab_settings, "")
self.Tab_pid = QtGui.QWidget()
self.Tab_pid.setObjectName("Tab_pid")
self.verticalLayout_8 = QtGui.QVBoxLayout(self.Tab_pid)
self.verticalLayout_8.setSpacing(0)
self.verticalLayout_8.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.scrollArea_2 = QtGui.QScrollArea(self.Tab_pid)
self.scrollArea_2.setWidgetResizable(True)
self.scrollArea_2.setObjectName("scrollArea_2")
self.scrollAreaWidgetContents_2 = QtGui.QWidget()
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 569, 494))
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
self.PID_tab_layout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents_2)
self.PID_tab_layout.setSpacing(0)
self.PID_tab_layout.setContentsMargins(0, 0, 0, 0)
self.PID_tab_layout.setObjectName("PID_tab_layout")
self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
self.verticalLayout_8.addWidget(self.scrollArea_2)
self.tabWidget.addTab(self.Tab_pid, "")
self.verticalLayout.addWidget(self.tabWidget)
self.retranslateUi(Form)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Form)
Form.setTabOrder(self.tabWidget, self.graphicsView)
Form.setTabOrder(self.graphicsView, self.scrollArea_2)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsView.setToolTip(QtGui.QApplication.translate("Form", "decimal point", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsView.setStatusTip(QtGui.QApplication.translate("Form", "Accuracy can be increased at lower temperatures", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_Program), QtGui.QApplication.translate("Form", "Program", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_alarms), QtGui.QApplication.translate("Form", "Alarms", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_settings), QtGui.QApplication.translate("Form", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_pid), QtGui.QApplication.translate("Form", "PID", None, QtGui.QApplication.UnicodeUTF8))
because there is an answer - which reasonably asks me to delete my answer - I have to answer the question with my own answer to close it. After waiting 3 days... hopefully I can delete it - but probably not as it has an answer..
I downloaded and tried your application and saw it more or less working
(although the cursor position gets wrong in test_toplevel.py).
After removing graph.inhibit_edit = False also the cursor stays right.
So is the problem already solved? Then it would be very helpful to remove your
question or mak it as resolved. Or please clarify the question.