Repository: pyqtgraph/pyqtgraph

Issue:
GraphicsWindow may be garbage collected too early
Consider the following code:

```python3
import pyqtgraph as pg

def plot():
    pg.GraphicsWindow()

plot()

pg.QtGui.QApplication.exec_()
```
I would expect a GraphicsWindow to show up, but it does not. It does when preventing garbage collection:

```python3
import pyqtgraph as pg

def plot():
    return pg.GraphicsWindow()

a = plot()

pg.QtGui.QApplication.exec_()
```

So there is an issue with garbage collection of GraphicsWindows.


Discussion:
I've come to expect this behavior with Qt... I even sometimes keep a module-level list outside of any functions to hold any persistent windows, that way I don't have to always return them. This is less problematic when using classes where you typically make UI objects an instance attribute of the class, so the reference lives as long as the instance does.
Right, this is a byproduct of the Qt event loop, I'm reluctant to try and manipulate how the garbage collector does things, as if stopped doing its thing, it would create far more massive head aches.

I would certainly entertain any suggested solutions though.
Example to show this isn't specific to `GraphicsWindow` or pyqtgraph:

```python
from PyQt5 import QtWidgets

app = QtWidgets.QApplication([])

def make_window():
    win = QtWidgets.QMainWindow()
    win.show()
    return win # comment out to compare

win = make_window()
app.exec_()
```

Resolving patch:
diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py
--- a/pyqtgraph/widgets/GraphicsView.py
+++ b/pyqtgraph/widgets/GraphicsView.py
@@ -15,6 +15,7 @@
 
 from ..Point import Point
 import sys, os
+import warnings
 from .FileDialog import FileDialog
 from ..GraphicsScene import GraphicsScene
 import numpy as np
@@ -396,5 +397,11 @@ def pixelSize(self):
         
     def dragEnterEvent(self, ev):
         ev.ignore()  ## not sure why, but for some reason this class likes to consume drag events
-        
 
+    def _del(self):
+        if self.parentWidget() is None and self.isVisible():
+            msg = "Visible window deleted. To prevent this, store a reference to the window object."
+            warnings.warn(msg, RuntimeWarning, stacklevel=2)
+
+if sys.version_info[0] == 3 and sys.version_info[1] >= 4:
+    GraphicsView.__del__ = GraphicsView._del


Test patch:
diff --git a/pyqtgraph/graphicsItems/tests/test_ImageItem.py b/pyqtgraph/graphicsItems/tests/test_ImageItem.py
--- a/pyqtgraph/graphicsItems/tests/test_ImageItem.py
+++ b/pyqtgraph/graphicsItems/tests/test_ImageItem.py
@@ -121,7 +121,7 @@ def test_ImageItem(transpose=False):
     assertImageApproved(w, 'imageitem/resolution_with_downsampling_y', 'Resolution test with downsampling across y axis.')
     assert img._lastDownsample == (1, 4)
     
-    view.hide()
+    w.hide()
 
 def test_ImageItem_axisorder():
     # All image tests pass again using the opposite axis order
diff --git a/pyqtgraph/graphicsItems/tests/test_ROI.py b/pyqtgraph/graphicsItems/tests/test_ROI.py
--- a/pyqtgraph/graphicsItems/tests/test_ROI.py
+++ b/pyqtgraph/graphicsItems/tests/test_ROI.py
@@ -147,6 +147,7 @@ def check_getArrayRegion(roi, name, testResize=True, transpose=False):
     # allow the roi to be re-used
     roi.scene().removeItem(roi)
 
+    win.hide()
 
 def test_PolyLineROI():
     rois = [
@@ -228,5 +229,6 @@ def test_PolyLineROI():
         r.setState(initState)
         assertImageApproved(plt, 'roi/polylineroi/'+name+'_setstate', 'Reset ROI to initial state.')
         assert len(r.getState()['points']) == 3
-        
-    
\ No newline at end of file
+    
+    plt.hide()
+