新信号连接到旧插槽而不是单独的插槽

问题描述 投票:0回答:1

我正在尝试标记数据跟踪的 x 跨度,并使用 tagNames、起始 x 值和结束 x 值填充表。我正在使用“突出显示”对象的字典来跟踪 x 跨度,以防以后需要对其进行编辑(增加或减少)。字典将 x 起始值映射到突出显示对象,因为 x 起始值预计是唯一的(标记的 x 跨度没有重叠)。

为了做到这一点,当用户编辑表格上的单元格时,我会发出一个信号。第一个信号连接的函数发出另一个信号(理想情况下是 xStart 与 xEnd 是否发生更改,但到目前为止我只实现了 xStart),这实际上改变了跨度的外观以匹配编辑。

几周前我问过类似的问题,但未能得到答案。老问题在这里:PyQT5 插槽参数在第一次调用后未更新。为了响应那里给出的提示,我写了以下示例:

import sys

from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtGui import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets
from functools import partial

class Window(QMainWindow):

    def __init__(self, parent = None):
        super(Window, self).__init__(parent)
        self.resize(1600, 800)
        self.MyUI()
        
    def MyUI(self):

        canvas = Canvas(self, width=14, height=12, dpi=100)
        canvas.move(0,0)

 #use this object in the dictionary to hold onto all the spans. 
class highlight:
    def __init__(self, tag, xStart, xEnd, highlightObj):
        self.tag = tag
        self.xStart = xStart
        self.xEnd = xEnd
        self.highlightObj = highlightObj

class Canvas(FigureCanvas):
    def __init__(self, parent, width = 14, height = 12, dpi = 100):
        Plot = Figure(figsize=(width, height), dpi=dpi)
        self.Axes = Plot.add_subplot(111)
        self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
        self.rowCount = 0
        super().__init__(Plot)
        self.setParent(parent)

        
        ##add all relevant lines to plot 
        self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
        self.Axes.set_xlabel('Frame', fontsize = 10) 
        self.Axes.grid()
        self.Axes.set_aspect(1)
        Plot.canvas.draw()

        
        self.highlights = {} #empty dictionary to store all the tags.

        ##define a table to hold the values postselection
        self.taggingTable = QTableWidget(self)
        self.taggingTable.setColumnCount(3)
        self.taggingTable.setRowCount(100)
        self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
        self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])


        Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal", 
                                        interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
        self.draw_idle()

        self.taggingTable.selectionModel().selectionChanged.connect(self.onCellSelect)
        self.draw_idle()

        ##highlighting adds a highlight item to the directory. 
    def onHighlight(self, xStart, xEnd):
        tagName = "No Tag"
        self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
        self.taggingTable.setItem(self.rowCount, 1, QTableWidgetItem(str(int(xStart))))
        self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
        self.rowCount = self.rowCount + 1
        highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
        self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
        self.draw_idle()
                
    def xStartChanged(self, xStart, rowVal):
        if self.inCounter == 0:
            print("xStart in slot: ", xStart)
            xEnd = self.highlights[xStart].xEnd
            xStartNew = int(self.taggingTable.item(rowVal, 1).text())
            self.highlights[xStart].highlightObj.remove() #remove old from the plot
            del self.highlights[xStart]                   #remove old from directory
            highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
            self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
            self.taggingTable.clearSelection() #deselect value from table
            self.draw_idle()
        self.inCounter = self.inCounter + 1

    def onCellSelect(self):
        index = self.taggingTable.selectedIndexes()
        if len(index) != 0:
            rowVal = index[0].row()
            if not (self.taggingTable.item(rowVal, 1) is None):
                xStart = int(self.taggingTable.item(rowVal, 1).text())
                print("--------------")
                print("xStart in signal: ", xStart)
                self.inCounter = 0
                self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))        
    
    


app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()

我运行的测试是当我突出显示两条痕迹时:

two traces and their table values

然后我成功更改了第一条轨迹:

editing first trace

但是,当我尝试编辑第二条跟踪时,程序崩溃了:

editing second trace before hitting enter

为了调试,我尝试检查发送和接收的信号。它产生以下输出:

--------------
xStart in signal:  0
xStart in slot:  0 ##First slot call gets correct signal 
--------------
xStart in signal:  3
xStart in slot:  0 ## Second slot gets the first signal instead of the second
Traceback (most recent call last):
  File "//Volumes/path/file.py", line 105, in <lambda>
    self.taggingTable.itemChanged.connect(lambda: self.xStartChanged(xStart, rowVal))        
  File "//Volumes/path/file.py", line 86, in xStartChanged
    xEnd = self.highlights[xStart].xEnd
KeyError: 0
zsh: abort      python Volumes/path file.py

我尝试使用有关独特连接的在线信息,但我不确定如何实施它们。预先感谢您的帮助。

python matplotlib pyqt5 signals-slots qtablewidget
1个回答
2
投票

您似乎需要的是表格小部件信号,只要对特定列进行更改,该信号就会发出一个项目其旧文本值。不幸的是, itemChanged 信号并不真正合适,因为它不指示 what 发生了变化,并且不提供以前的值。因此,要解决此限制,一种解决方案是子类化 QTableWidget / QTableWidgetItem 并发出具有所需参数的自定义信号。这将完全回避多个信号槽连接的问题。

子类的实现非常简单:

class TableWidgetItem(QTableWidgetItem):
    def setData(self, role, value):
        oldval = self.text()
        super().setData(role, value)
        if role == Qt.EditRole and self.text() != oldval:
            table = self.tableWidget()
            if table is not None:
                table.itemTextChanged.emit(self, oldval)

class TableWidget(QTableWidget):
    itemTextChanged = pyqtSignal(TableWidgetItem, str)

下面是基于您的示例的基本演示,展示了如何使用它们。 (请注意,我也没有尝试处理 xEnd,因为这超出了当前问题的范围)。

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

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets

class Window(QMainWindow):
    def __init__(self, parent = None):
        super(Window, self).__init__(parent)
        self.resize(1600, 800)
        self.MyUI()

    def MyUI(self):
        canvas = Canvas(self, width=14, height=12, dpi=100)
        canvas.move(0,0)

# CUSTOM SUBCLASSES

class TableWidgetItem(QTableWidgetItem):
    def setData(self, role, value):
        oldval = self.text()
        super().setData(role, value)
        if role == Qt.EditRole and self.text() != oldval:
            table = self.tableWidget()
            if table is not None:
                table.itemTextChanged.emit(self, oldval)

class TableWidget(QTableWidget):
    itemTextChanged = pyqtSignal(TableWidgetItem, str)


 #use this object in the dictionary to hold onto all the spans.
class highlight:
    def __init__(self, tag, xStart, xEnd, highlightObj):
        self.tag = tag
        self.xStart = xStart
        self.xEnd = xEnd
        self.highlightObj = highlightObj

class Canvas(FigureCanvas):
    def __init__(self, parent, width = 14, height = 12, dpi = 100):
        Plot = Figure(figsize=(width, height), dpi=dpi)
        self.Axes = Plot.add_subplot(111)
        self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
        self.rowCount = 0
        super().__init__(Plot)
        self.setParent(parent)

        ##add all relevant lines to plot
        self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
        self.Axes.set_xlabel('Frame', fontsize = 10)
        self.Axes.grid()
        self.Axes.set_aspect(1)
        Plot.canvas.draw()

        self.highlights = {} #empty dictionary to store all the tags.

        ##define a table to hold the values postselection
        # USE CUSTOM TABLE SUBCLASS
        self.taggingTable = TableWidget(self)
        self.taggingTable.setColumnCount(3)
        self.taggingTable.setRowCount(100)
        self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
        self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])
        # CONNECT TO CUSTOM SIGNAL
        self.taggingTable.itemTextChanged.connect(self.xStartChanged)

        Canvas.span = mwidgets.SpanSelector(self.Axes, self.onHighlight, "horizontal",
                                        interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)
        self.draw_idle()

        ##highlighting adds a highlight item to the directory.
    def onHighlight(self, xStart, xEnd):
        tagName = "No Tag"
        self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
        # USE CUSTOM ITEM SUBCLASS
        self.taggingTable.setItem(self.rowCount, 1, TableWidgetItem(str(int(xStart))))
        self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
        self.rowCount = self.rowCount + 1
        highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
        self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
        self.draw_idle()

    def xStartChanged(self, item, oldVal):
        try:
            # VALIDATE NEW VALUES
            xStart = int(oldVal)
            xStartNew = int(item.text())
        except ValueError:
            pass
        else:
            print("xStart in slot: ", xStart)
            xEnd = self.highlights[xStart].xEnd
            self.highlights[xStart].highlightObj.remove() #remove old from the plot
            del self.highlights[xStart]                   #remove old from directory
            highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5) #add new to plot
            self.highlights[xStartNew] = highlight("No tagName", xStartNew, xEnd, highlightObj) #add new to directory
            self.taggingTable.clearSelection() #deselect value from table
            self.draw_idle()


app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()
© www.soinside.com 2019 - 2024. All rights reserved.