首页 > 解决方案 > matplotlib - 如果有多个事件处理程序,你可以设置优先级吗?

问题描述

我正在使用 PyQt5 构建一个工具,该工具将允许用户在 matplotlib_widget 中单击时拖动矩形。问题是我想知道点击了哪个矩形。

由于我的画布和我的矩形是两个不同的类,我很难在它们之间进行正确的交流。为了解决这个问题,我在矩形画布的“button_click_event”上添加了一个事件处理程序,以将其位置写入 .txt 文件。另一方面,我还将“button_click_event”连接到我的画布,这是为了读取 .txt 并在先前存储的列表中验证该位置是否存在。如果是,则表示点击的是这个特定的矩形。

问题:读取 .txt 文件的画布的事件处理程序在写入它的处理程序之前被调用。

是否可以对 figure.canvas 的事件设置优先级?

这是我的具体问题的一个最低限度的工作示例。你会注意到当我想要的功能做某事时我做了打印。如果你双击并创建 2 个矩形,你应该注意到在单击矩形 1 后单击矩形 0,它会说你单击了矩形 1。这是“saveInFilie”功能的演示之后调用文件被读取。

import matplotlib.pyplot as plt
from matplotlib import patches


class DraggableRectangle:
lock = None  # only one can be animated at a time

def __init__(self, rect):
    self.rect = rect
    self.press = None
    self.background = None

def connect(self):
    'connect to all the events we need'
    self.cidpress = self.rect.figure.canvas.mpl_connect(
        'button_press_event', self.on_press)
    self.cidrelease = self.rect.figure.canvas.mpl_connect(
        'button_release_event', self.on_release)

def on_press(self, event):
    'on button press we will see if the mouse is over us and store some data'
    if event.inaxes != self.rect.axes: return
    if DraggableRectangle.lock is not None: return
    contains, attrd = self.rect.contains(event)
    if not contains: return
    print('Held @', self.rect.xy)
    x0, y0 = self.rect.xy
    self.saveInFile(str(self.rect.xy))
    print("click write succeded")
    self.press = x0, y0, event.xdata, event.ydata
    DraggableRectangle.lock = self

    # draw everything but the selected rectangle and store the pixel buffer
    canvas = self.rect.figure.canvas
    axes = self.rect.axes
    self.rect.set_animated(True)
    canvas.draw()
    self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

    # now redraw just the rectangle
    axes.draw_artist(self.rect)

    # and blit just the redrawn area
    canvas.blit(axes.bbox)

def on_release(self, event):
    'on release we reset the press data'
    if DraggableRectangle.lock is not self:
        return

    x0, y0 = self.rect.xy
    self.press = None
    DraggableRectangle.lock = None

    # turn off the rect animation property and reset the background
    self.rect.set_animated(False)
    self.background = None
    self.saveInFile(str(self.rect.xy))
    print("release write succeded\n")
    # redraw the full figure
    self.rect.figure.canvas.draw()
    #print("Realeased @", x0, y0)

def saveInFile(self, drop):
    filename = "pos.txt"
    with open(filename, "w") as file:
        file.write(drop)
        file.close()


class MyFigure:

def __init__(self):
    # Figure initialisation
    self.fig = plt.figure()
    self.axes = self.fig.add_subplot(1, 1, 1)
    self.fig.subplots_adjust(0, 0, 1, 1)
    self.axes.set_frame_on(False)
    self.axes.invert_yaxis()
    self.axes.axis('off')
    self.axes.xaxis.set_visible(False)
    self.axes.yaxis.set_visible(False)

    # Connections
    self.ciddouble = self.fig.canvas.mpl_connect('button_press_event', self.createRectangle)
    self.CheckClick = self.fig.canvas.mpl_connect('button_press_event', self.checkRectangleOnClick)
    self.CheckRelease = self.fig.canvas.mpl_connect('button_release_event', self.checkRectangleOnRelease)

    # Variables
    self.devices = []
    self.drs = []
    self.sensorPixelSize = [50, 50]

def createRectangle(self, event):
    relPosX = event.x / (self.fig.get_size_inches()[0] * self.fig.dpi)
    relPosY = 1 - (event.y / (self.fig.get_size_inches()[1] * self.fig.dpi))
    relSizeX = self.sensorPixelSize[0] / (self.fig.get_size_inches()[0] * self.fig.dpi)
    relSizeY = self.sensorPixelSize[1] / (self.fig.get_size_inches()[1] * self.fig.dpi)
    absPoxX = relPosX * (self.fig.get_size_inches()[0] * self.fig.dpi)
    absPosY = relPosY * (self.fig.get_size_inches()[1] * self.fig.dpi)
    absSizeX = self.sensorPixelSize[0]
    absSizeY = self.sensorPixelSize[1]

    if event.dblclick and event.button == 1:

        rect = self.axes.add_artist(
            patches.Rectangle((relPosX, relPosY), relSizeX, relSizeY, edgecolor='black', facecolor='black',
                              fill=True))
        dr = DraggableRectangle(rect)
        dr.connect()
        print(dr.rect.xy)
        self.drs.append(dr)
        self.fig.canvas.draw()
        local = ["", "", (relPosX, relPosY), relSizeX, relSizeY, (absPoxX, absPosY), absSizeX, absSizeY]
        self.devices.append(local)

def checkRectangleOnClick(self, event):
    filename = "pos.txt"
    with open(filename, "r") as file:
        varia = file.read()
        file.flush()

    for i in range(len(self.devices)):
        if str(varia) == str(self.devices[i][2]):
            print("Clicked rectangle #%i" % i)
            self.clickedIndex = i

        else:
            self.clickedIndex = None

def checkRectangleOnRelease(self, event):
    filename = "pos.txt"
    with open(filename, "r") as file:
        varia = file.read()
        file.flush()

    for i in range(len(self.devices)):
        if str(varia) == str(self.devices[i][2]):
            print("Realeased rectangle #%i" % i)
            self.clickedIndex = i

        else:
            self.clickedIndex = None


fig = MyFigure()
plt.show()

标签: pythonmatplotlibevent-handling

解决方案


解决了这个问题。为了给事件处理程序设置一个“优先级”,我只从事件中调用了一个函数。从那时起,当处理程序完成其目的时,它会调用另一个不再连接的处理程序,然后第二个处理程序调用第三个处理程序,依此类推。

我在 DraggableRectangle 类中添加了我的个人画布类“MyFigure”的输入。因此,我现在可以从 DraggableRectangle 调用 MyFigure 的函数。所以,在 On_Click 中,在 'saveInFile' 函数之后,我只是直接调用函数 checkRectangleOnClick 函数,然后调用 checkRectangleOnRelease。我从“button_click_event”断开了这些功能。没有更多的问题,一切都在它应该发生的时候发生。


推荐阅读