80571


pyqt5とmatplotlibを使い、グラフをGUIに表示します。この時グラフを左クリックしながらドラッグすることで視点変更、右クリックしながらドラッグすることでスケール変更が出来ます。ここでグラフをダブルクリックすると保存フォルダーが開かれ、グラフの図を保存することが出来ます。しかし、保存フォルダーを閉じた後にマウスを動かすとグラフの視点が変更されます。恐らく保存フォルダーを開いたことにより、左クリックのリリースイベントが起こらずに、グラフをクリックしたままドラッグしている扱いになっていると思われます。どのように修正すればよいのでしょうか。

* * *

**表示されるGUI**  
[![画像](https://i.stack.imgur.com/yp4Uj.png)](https://i.stack.imgur.com/yp4Uj.png)

**ソースコード**

[code]

    import sys
    from PyQt5.QtWidgets import (QDialog, QApplication, QVBoxLayout, 
                                 QPushButton, QFileDialog)
    from matplotlib.figure import Figure
    import numpy as np
    import matplotlib.pyplot as plt
    from PIL import Image, ImageOps
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt5 import NavigationToolbar2QT as NavigationToolbar
    from mpl_toolkits.mplot3d import Axes3D
    
    
    class MyCanvas(FigureCanvas):
        def __init__(self, parent=None):
            width=6
            height=6
            dpi=100
    
            self.check_label=True
    
            self.color="#ffffff"
            self.fig = plt.figure(figsize=(width, height), dpi=dpi)
            self.canvas = FigureCanvas(self.fig)
            
            FigureCanvas.__init__(self, self.fig)
            self.setParent(parent)
            
            self.axes = Axes3D(self.fig)
            self.axes.set_box_aspect((1,1,1))
            self.axes.dist=12
    
            self.mpl_connect('button_press_event', self.on_button_press) 
            self.mpl_connect('button_release_event', self.on_button_release)
    
        def on_button_press(self,event):
            if event.button==1:
                if event.dblclick:
                    self.set_save()
                else:
                    pass
                
        def on_button_release(self,event):
            print("button release")
    
        def set_save(self):
            filename,_ = QFileDialog.getSaveFileName(self, "save",filter="PNG Files(*.png) ;; JPG Files(*.jpg) ;; All Files ()")
            if len(filename) == 0:
                return
            self.fig.savefig(filename)
            
    class MyMplCanvas(MyCanvas):
        def __init__(self, parent=None):
            self.win=parent 
            super(MyMplCanvas,self).__init__(parent)
    
    class Main(QDialog):
        def __init__(self, parent=None):
            super(Main, self).__init__(parent)
            
            x = np.arange(-3, 3, 0.25)
            y = np.arange(-3, 3, 0.25)
            X, Y = np.meshgrid(x, y)
            Z = np.sin(X)+ np.cos(Y)
            
            self.canvas=MyMplCanvas(self)
            self.canvas.axes.plot_surface(X,Y,Z,cmap='jet')
            self.canvas.axes.set_title("graph",fontsize=20,c="k")
            
            layout1=QVBoxLayout()
            layout1.addWidget(self.canvas)
            self.setLayout(layout1)
            
            self.show()
    
            
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        win = Main()
        #sys.exit(app.exec_())
        app.exec_()
    
[/code]


CC BY-SA 4.0
2021-08-03T14:13:14.327
80569
2021-08-05T10:23:00.090
26529
question
python matplotlib pyqt5
pyqt5でグラフをダブルクリックしたときに保存フォルダーを開き、グラフの保存をしたい


> 恐らく保存フォルダーを開いたことにより、左クリックのリリースイベントが起こらずに、グラフをクリックしたままドラッグしている扱いになっていると思われます。

見たところ、まさにその通りという動きをしていますね。

`on_button_press`, `on_button_release` それぞれの `event.guiEvent.type()` を通じて、
バックエンドとなる Qt のイベントを見てみると、

  * `QtCore.QEvent.MouseButtonPress`
  * `QtCore.QEvent.MouseButtonRelease`
  * `QtCore.QEvent.MouseButtonDblClick`
  * `QtCore.QEvent.MouseButtonRelease`

の順に呼び出されていることがわかります。

ところが、 `MouseButtonDblClick` の処理中に `self.set_save()` を呼び出してしまうと、 その後の
`MouseButtonRelease` の処理を matplotlib 側が見落としてしまうようです。

* * *

さて、 どう修正すれば良いかについてですが、 `self.set_save()` の処理の前に `MouseButtonRelease`
のイベントがトリガーされれば、とりあえず目的を果たせそうです。

いくつかやり方は考えられそうですが、手っ取り早そうなのは、以下の 2通り 思いつきます。

  1. ダブルクリックでフラグだけ立てて、 `on_button_release` で `set_save()` する
  2. `on_button_press` で `set_save()` 呼ぶ前に、 強制的に Qt の `MouseButtonRelease` イベントをトリガーさせる

このうち (2) の実装例ですが、 `MyCanvas` が継承している `FigureCanvasQTAgg` は、しれっと
`QtWidgets.QWidget` を継承しているので、
[`QtTest.QTest.mouseRelease`](https://doc.qt.io/qt-5/qtest.html#mouseRelease)
を使って以下のように呼び出すことが可能です。

[code]

    from PyQt5 import QtTest, QtCore
    
    ...
    
        def on_button_press(self,event):
            if event.button==1:
                if event.dblclick:
                    QtTest.QTest.mouseRelease(self, QtCore.Qt.LeftButton)
                    self.set_save()
    
[/code]


CC BY-SA 4.0
2021-08-03T17:28:26.693
80571
2021-08-04T16:19:05.170
2021-08-04T16:19:05.170
8237
8237
80569
answer


既に解決済みですが、参考までに別回答を投稿します。

原因としてはダブルクリックの2回目の mouse release event が QFileDialog widget に取られてしまうからです。

[QWidget
Events](https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QWidget.html#events)

> mouseReleaseEvent() is called when a mouse button is released. A widget
> receives mouse release events when it has received the corresponding mouse
> press event. This means that if the user presses the mouse inside your
> widget, then drags the mouse somewhere else before releasing the mouse
> button, your widget receives the release event. **There is one exception: if
> a popup menu appears while the mouse button is held down, this popup
> immediately steals the mouse events**.

FigureCanvasQTAgg には各種 event の callback function が登録されている `callbacks`
attribute がありますので、これを使って mouse release event に紐付けられている callback function
を実行させることができます。

[code]

        def on_button_press(self, event):
            if event.button == 1:
                if event.dblclick:
                    self.callbacks.process('button_release_event', event)
                    self.set_save()
                else:
                    pass
    
[/code]


CC BY-SA 4.0
2021-08-04T03:11:53.097
80583
2021-08-04T03:11:53.097
47127
80569
answer