MS Edge backend fails in Notebook (other than first page)

Well, the ticket page states:

will always stay invisible if its parent is hidden when the wxWebView is constructed

This suggests that the workaround is to create the webview only delayed, when the page is visible. Doesn’t this work?

Sort of. The original example worked for me when I delayed the creation of the notebook pages (which contained the WebView) until after the MainLoop started, and separated the creation of multiple pages by a few ms, so any any async work the webview needs to do has a chance to be completed before the next webview. That worked for me, but, IIUC, not for @cutright.

Although my workaround is not working now that I’ve tried it again…

That PR was included in the wxWidgets snapshot used for the wxPython 4.1.1 release. Since the issue is still happening then it looks like that PR didn’t fix it.

Here is another workaround. If the webview itself is the page widget (IOW, no wx.Panel between the webview and the notebook) then it’s able to handle multiple webviews in the notebook with no apparent issues. Here is an updated sample. Notice the flags at the top that let you choose between using delay/no-delay and panel/no-panel.

import wx
import wx.html2 as webview

print(wx.version())

NotebookClass = wx.Notebook
BACKEND = webview.WebViewBackendEdge
USE_DELAY = False
NO_PANEL = True

class DVHAMainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.notebook_main_view = NotebookClass(self, wx.ID_ANY)
        self.tab_keys = ['Browser 1', 'Browser 2', 'Browser 3']

        global BACKEND
        if not webview.WebView.IsBackendAvailable(BACKEND):
            BACKEND = webview.WebViewBackendDefault
        print(f'Using backend: {BACKEND}')

        if USE_DELAY:
            delay = 10
            for key in self.tab_keys:
                wx.CallLater(delay, self.make_page, key)
                delay += 50
        else:
            for key in self.tab_keys:
                self.make_page(key)

        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.notebook_main_view, 1, wx.EXPAND, 0)

        self.SetSizer(sizer)
        self.Layout()
        self.Center()

    def make_page(self, title):
        if NO_PANEL:
            plot = page = webview.WebView.New(self.notebook_main_view, backend=BACKEND)
        else:
            page = wx.Panel(self.notebook_main_view)
            plot = webview.WebView.New(page, backend=BACKEND)
            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.Add(plot, 1, wx.EXPAND)
            page.SetSizer(sizer)

        self.notebook_main_view.AddPage(page, title)
        plot.LoadURL("http://www.google.com")

class MainApp(wx.App):
    def OnInit(self):
        self.SetAppName('DVH Analytics')
        self.frame = DVHAMainFrame(None, wx.ID_ANY, "", size=(800,600))
        self.SetTopWindow(self.frame)
        # InspectionTool().Show()
        self.frame.Show()
        return True

def start():
    app = MainApp()
    app.MainLoop()

if __name__ == "__main__":
    start()

I have just tried and the “single browser fail” works for me if the page is visible:

  • create panel
  • add to notebook
  • make visible (SetSelection)
  • create webview

I.e. just re-ordered and added SetSelection:

    def make_web_page(self, title):
        page = wx.Panel(self.notebook_main_view)
        self.notebook_main_view.AddPage(page, title)
        self.notebook_main_view.SetSelection(self.notebook_main_view.PageCount-1)
        plot = webview.WebView.New(page, backend=BACKEND)
        plot.LoadURL("http://www.google.com")
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(plot, 1, wx.EXPAND)
        page.SetSizer(sizer)

This does not help when you want multiple browser tabs.
I could make this work with on-demand creation, though.
I would consider this a better style anyway when working with resource intensive widgets.
When creating the browsers in an EVT_NOTEBOOK_PAGE_CHANGING handler, it fails, as expected as then the parent is not visible.

Here’s the full code:

import wx
import wx.html2 as webview
# from wx.lib.inspection import InspectionTool
# from wx.lib.agw.flatnotebook import FlatNotebook

NotebookClass = wx.Notebook
# NotebookClass = FlatNotebook
#BACKEND = webview.WebViewBackendDefault
BACKEND = webview.WebViewBackendEdge


class DVHAMainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)

        self.notebook_main_view = NotebookClass(self, wx.ID_ANY)
        self.tab_keys = ['Browser 1', 'Browser 2', 'Browser 3']
        self._created = set()

        for key in self.tab_keys:
            self.make_web_page(key)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.notebook_main_view, 1, wx.EXPAND, 0)

        self.SetSizer(sizer)
        self.Layout()
        self.Center()

        self.make_browser(self.tab_keys[0])
        self.notebook_main_view.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_selection)

    def make_web_page(self, title):
        # just add a panel, but not yet a browser
        page = wx.Panel(self.notebook_main_view)
        self.notebook_main_view.AddPage(page, title)

    def on_page_selection(self, event):
        # when page is selected, check whether the browser has been created already
        index = event.GetSelection()
        key = self.tab_keys[index]
        if not key in self._created:
            self.make_browser(key)
        event.Skip()

    def make_browser(self, key):
        # on-demand creation of the browser
        index = self.tab_keys.index(key)
        page = self.notebook_main_view.GetPage(index)
        plot = webview.WebView.New(page, backend=BACKEND)
        plot.LoadURL("http://www.google.com")
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(plot, 1, wx.EXPAND)
        page.SetSizer(sizer)
        page.Layout()
        self._created.add(key)


class MainApp(wx.App):
    def OnInit(self):
        self.SetAppName('DVH Analytics')
        self.frame = DVHAMainFrame(None, wx.ID_ANY, "", size=(800,600))
        self.SetTopWindow(self.frame)
        # InspectionTool().Show()
        self.frame.Show()
        return True


def start():
    app = MainApp()
    app.MainLoop()


if __name__ == "__main__":
    start()

Thanks everyone for chiming in here. I wish I had more bandwidth to keep troubleshooting with you. I’ll try to give @Robin’s non wx.Panel a shot this weekend.

Just for context to the conversation, I’m hi-jacking the wx.html2 for fancier UI… using Bokeh to generate JS plots. So my application has several “web browsers” displaying different local html files.

Thanks for these workarounds, they work fine imho.

May I ask if it is possible to respond to several in-browser events?
F.e. opening a link in the default browser when a link is clicked inside of the webview.
Or if desktop notifications are supported?

There are some specific events: https://wxpython.org/Phoenix/docs/html/wx.html2.WebView.html#events-events-emitted-by-this-class

E.g. EVT_WEBVIEW_NAVIGATING

See also the HTML2_WebView.py demo.

Alternatively, you may serve the html pages locally or as a proxy via localhost.
But then you are at a point where you should ask yourself, whether your architecture makes sense…

Quick update: @Robin’s latest example works for me too. It’ll take some time to apply this approach to my application, but looks promising so far.

Unfortunately, @Robin’s example doesn’t work for my situation. My tabs contain other wx objects. Perhaps there’s another container object I could try other than wx.Panel? The screenshot here is using the IE backend.

Thanks, I will try these.
I don’t know if you know the “Franz” messenger.
This application basically serves multiple messenger pages, f.e. whatsapp web, Gmail in own tabs.
But since it is based on electron it’s very Ressource heavy.
I wanted to make something similar for myself.
And since many links are sent I want them to be opened in my default browser.
Maybe in a future version notifications might work as well :slight_smile:

@andileni Just my two cents, but I think you’d be better off leaving the tabs to the webview. Have you seen CEF Python? https://github.com/cztomczak/cefpython

@cutright Thanks for that hint.
The webview has the same function as cefpython, or did I understand something wrong?
Or do you mean cefpython offers more options?

@cutright:
Try the delayed loading whether it’s an option for you. Of course it introduces a short delay.

https://discuss.wxpython.org/t/ms-edge-backend-fails-in-notebook-other-than-first-page/35036/20?u=dietmarschwertberger

@DietmarSchwertberger Yeah… I tried a delay too, I went as high as 0.1s delays. But maybe I didn’t place it in the right spot, I’ll try again putting it immediately after the WebView creation.

@andileni I don’t have first hand experience coding with cef python, but someone demoed an app to me with it. He used wxPython just for a basic App and Frame. Then he embedded his VueJS web app in that. Super slick. The advantage here is you can have chromium embedded in the exe, so you wouldn’t have to make your users install MS Edge. I wish I could elaborate more than that.

Oh I see. Thanks for the elaboration.
I don’t know if it suits my needs, since most messengers are not embeddable via iframe, but I will definitely give it a try.

@cutright:
Sorry, should have written “on-demand”, not “delayed”. Please have a look at the previous, linked post.

Your on-demand example code works for me. It will take me a while to implement with my code though. Perhaps I should have picked a smaller project to learn wxPython, it’s a bit of a mess :slight_smile:

Would matplotlib have worked for your plots?
It’s a bit easier to integrate with wx.
I have included examples with wxGlade. The one showing the most integration features is at https://github.com/wxGlade/wxGlade/tree/master/examples/matplotlib3

(The weak point for business applications is the export to office applications, though, as MS still does not really support SVG and also matplotlib does not use the standard fonts of the OS.)