Repository: napari/napari

Issue:
nbscreenshot not always showing layerlist
## 🐛 Bug
as [jni noted](https://github.com/napari/napari/issues/1588#issuecomment-682233015) from my screenshot in #1588 (shown below) ... `nbscreenshot` is sometimes not showing the layer list... and possible other inconsistencies with the viewer? 
(cropped is a dask array in this case)
<img width="699" alt="91489668-2bb51600-e87f-11ea-81e6-6a7a4470de0a" src="https://user-images.githubusercontent.com/1609449/91723832-61e9e280-eb6a-11ea-8159-84e6ebb6c652.png">



Discussion:
i think there's some sort of race condition, cause if I simply re-run that cell above, commenting out the new viewer, then the screenshot is correct the second time that it gets run
@tlambert03 I have noticed this too. A race condition seems likely
just want to bump this issue again.  I was starting to create some magicgui docs, showing the result of adding a widget and layers, but it's not very helpful at the moment, since the layers don't appear (and actually I don't think added dock widgets do either?)
For the layer list i wonder if it is because it's our own custom qscrollarea. if we used a listview it might be better. not sure if that change is worth it before the coming layer groups though.

i'm not quite sure what to do if the issue is more general. i don't have any leads right now, but i've seen this too
ahh.. it's the gd event loop (it's _always_ the gd event loop :).  This is conceptually somewhat like https://github.com/napari/napari/issues/2029 actually.

If you do something like this:
```python
viewer = napari.Viewer()
viewer.add_points(None)
viewer.window.add_dock_widget(my_widget)
nbscreenshot(viewer)
```

then at the moment that nbscreenshot is called, none of the events that have been "queued up" by lines 2 and 3 have actually been triggered... since there _is no_ event loop until `napari.run` is called.

I think we can fix it just be forcing the event loop to process with `get_app().processEvents()` inside of `_repr_png_`.  That unfortunately requires a qt import outside of the qt folder... but I think that's the compromise we have to make at the moment for the fact that we rely on qt for our entire event loop.

Resolving patch:
diff --git a/napari/utils/notebook_display.py b/napari/utils/notebook_display.py
--- a/napari/utils/notebook_display.py
+++ b/napari/utils/notebook_display.py
@@ -3,9 +3,14 @@
 __all__ = ['nbscreenshot']
 
 
-def nbscreenshot(viewer, *, canvas_only=False):
+class NotebookScreenshot:
     """Display napari screenshot in the jupyter notebook.
 
+    Functions returning an object with a _repr_png_() method
+    will displayed as a rich image in the jupyter notebook.
+
+    https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html
+
     Parameters
     ----------
     viewer : napari.Viewer
@@ -14,22 +19,6 @@ def nbscreenshot(viewer, *, canvas_only=False):
         If True includes the napari viewer frame in the screenshot,
         otherwise just includes the canvas. By default, True.
 
-    Returns
-    -------
-    napari.utils.notebook_display.NotebookScreenshot
-        Napari screenshot rendered as rich display in the jupyter notebook.
-    """
-    return NotebookScreenshot(viewer, canvas_only=canvas_only)
-
-
-class NotebookScreenshot:
-    """Display napari screenshot in the jupyter notebook.
-
-    Functions returning an object with a _repr_png_() method
-    will displayed as a rich image in the jupyter notebook.
-
-    https://ipython.readthedocs.io/en/stable/api/generated/IPython.display.html
-
     Examples
     --------
     ```
@@ -69,9 +58,15 @@ def _repr_png_(self):
         """
         from imageio import imsave
 
+        from .._qt.qt_event_loop import get_app
+
+        get_app().processEvents()
         self.image = self.viewer.screenshot(canvas_only=self.canvas_only)
         with BytesIO() as file_obj:
             imsave(file_obj, self.image, format='png')
             file_obj.seek(0)
             png = file_obj.read()
         return png
+
+
+nbscreenshot = NotebookScreenshot
diff --git a/napari/viewer.py b/napari/viewer.py
--- a/napari/viewer.py
+++ b/napari/viewer.py
@@ -1,6 +1,12 @@
+from typing import TYPE_CHECKING
+
 from .components import ViewerModel
 from .utils import config
 
+if TYPE_CHECKING:
+    # helpful for IDE support
+    from ._qt.qt_main_window import Window
+
 
 class Viewer(ViewerModel):
     """Napari ndarray viewer.
@@ -22,7 +28,7 @@ class Viewer(ViewerModel):
     """
 
     # Create private variable for window
-    _window = None
+    _window: 'Window'
 
     def __init__(
         self,
@@ -47,7 +53,7 @@ def __init__(
 
     # Expose private window publically. This is needed to keep window off pydantic model
     @property
-    def window(self):
+    def window(self) -> 'Window':
         return self._window
 
     def update_console(self, variables):
diff --git a/napari/window.py b/napari/window.py
--- a/napari/window.py
+++ b/napari/window.py
@@ -6,6 +6,7 @@
 to server as a graphical user interface for napari.
 """
 
+__all__ = ['Window']
 
 try:
     from ._qt import Window


Test patch: