Observer not triggered for declared attribute

50 views
Skip to first unread message

cryzed

unread,
Oct 3, 2018, 5:03:07 PM10/3/18
to Enaml
Hello, I am trying to minimally wrap PyQt5's QWebEngineView:

```from PyQt5.QtWebEngineWidgets import QWebEngineView
from atom.api import Unicode, observe, set_default
from enaml.core.declarative import d_
from enaml.widgets.api import RawWidget


class WebEngineViewWidget(RawWidget):
    __slots__ = '__weakref__'

    url = d_(Unicode())
    hug_width = set_default('ignore')
    hug_height = set_default('ignore')

    def create_widget(self, parent):
        return QWebEngineView(parent)

    @observe('url')
    def _update_proxy(self, change):
        print('change', change)
        super()._update_proxy(change)
```

In my *.enaml-file I have this line

```
        WebEngineViewWidget:
            url << folder.image_path
```

My model (folder-)model looks like this:

```import os

import magic
import natsort
from atom.api import Atom, Bytes, Int, List, Str, observe
from enaml.image import Image


class ImageFolder(Atom):
    path = Str()
    image_data = Bytes()
    image_path = Str()

    items = List(str)
    index = Int(default=0)
    _supported_formats = set(Image.format.items).union({'jpeg'}) - {'auto'}

    def _load_image(self):
        if self.items:
            self.image_path = self.items[self.index]
            with open(self.image_path, 'rb') as file:
                self.image_data = file.read()

    @observe('path')
    def _on_path(self, change):
        # Clear image, items, and reset index
        self.image_data = b''
        self.items.clear()
        self.index = 0

        # If the path is empty or doesn't exist just return
        path = change['value']
        if not os.path.isdir(path):
            return

        # Collect all images recursively
        for root, folders, file_names in os.walk(path):
            for file_name in file_names:
                path = os.path.join(root, file_name)
                mime = magic.from_file(path, mime=True)
                kind, format = mime.split('/', 1)
                if kind == 'image' and format in self._supported_formats:
                    self.items.append(path)

        self.items = natsort.natsorted(self.items)
        self._load_image()

    def next(self):
        self.index += 1
        if self.index >= len(self.items):
            self.index = 0
        self._load_image()

    def previous(self):
        self.index -= 1
        if self.index < 0:
            self.index = len(self.items) - 1
        self._load_image()
```

I know that image_path definitely changes, but it never seems to trigger the observer method. The observer method successfully works for other (default attributes):
```change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'background', 'value': None}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'foreground', 'value': None}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'font', 'value': None}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'minimum_size', 'value': Size(width=-1, height=-1)}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'maximum_size', 'value': Size(width=-1, height=-1)}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'tool_tip', 'value': ''}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'status_tip', 'value': ''}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'enabled', 'value': True}
change {'type': 'create', 'object': <widgets.WebEngineViewWidget object at 0x7f7812e273a8>, 'name': 'visible', 'value': True}```

What am I doing wrong here?

frmdstryr

unread,
Oct 3, 2018, 5:40:45 PM10/3/18
to Enaml
Is there a reason you're not using enaml's WebView which uses Qt's web engine (unlike the Html widget)? 

The code is not working because `_update_proxy` attempts to trigger `set_<name>` on the proxy. If it is not defined (as is the case here) it does nothing.  For RawWidgets just call the methods on the proxy directly. For example

  @observe('url')
    def _update_url(self, change):
          self.proxy.widget.setUrl(QUrl(change['value']))

cryzed

unread,
Oct 3, 2018, 6:09:39 PM10/3/18
to Enaml
I think I must have missed that widget and will probably use it. But this still hasn't fixed my problem, even after adjusting my code:

```from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEngineView
from atom.api import Unicode, observe, set_default
from enaml.core.declarative import d_
from enaml.widgets.api import RawWidget


class WebEngineViewWidget(RawWidget):
    __slots__ = '__weakref__'

    url = d_(Unicode())
    hug_width = set_default('ignore')
    hug_height = set_default('ignore')

    def create_widget(self, parent):
        return QWebEngineView(parent)

    @observe('url')
    def _update_url(self, change):
        print('update', change)
        self.proxy.widget.setUrl(QUrl(change['value']))
```

The print (what I was really after) is never triggered, nor is the URL of the proxy changed. To be complete:

Do you maybe know now what's going wrong? The update method is never called once, and I know for a fact that image_path/image_data do change (so observers bound to it should be triggered).

cryzed

unread,
Oct 3, 2018, 6:12:48 PM10/3/18
to Enaml
Ah yes, and the reason why I wasn't using the WebView widget is because it uses the outdated PyQt5.QtWebKitWidgets, instead of the still developed and more modern WebEngine.

cryzed

unread,
Oct 3, 2018, 6:31:43 PM10/3/18
to Enaml
Nevermind, I'm blind, just saw this line:


```
# Only needed for Qt 5.10+
from enaml.qt import QtWebEngineWidgets
```

Matthieu Dartiailh

unread,
Oct 3, 2018, 6:34:44 PM10/3/18
to cryzed, Enaml
Testing your code by adding a stupid button to manually change the url show that the observer is properly triggered. So you probably have another issue (but I cannot test quickly because I don’t have magic nor natsort).

Best 

Matthieu

--
You received this message because you are subscribed to the Google Groups "Enaml" group.
To unsubscribe from this group and stop receiving emails from it, send an email to enaml+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

cryzed

unread,
Oct 3, 2018, 6:36:32 PM10/3/18
to Enaml
Thank you for Matthieu & frmdstryr. I'll figure out the issue with the observer some other time, when I'm less frustrated, and use the WebEngine widget for now (which luckily works). Thanks!

frmdstryr

unread,
Oct 3, 2018, 9:18:21 PM10/3/18
to Enaml
There is no initial print because the url is not accessed so the initial read never occurs.

Typically widgets will sync the initial state in the init_widget method.

Reply all
Reply to author
Forward
0 new messages