wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.21k stars 509 forks source link

wx.EVT_ENTER_WINDOW not Triggering on Windows 11, working in Ubuntu & Artix #2539

Closed ClayShoaf closed 1 month ago

ClayShoaf commented 1 month ago

Operating system: Windows 11, Ubuntu 22.04, Artix wxPython version & source: 4.2.1, pip (pypi) Python versions & source:

Description of the problem: wx.EVT_ENTER_WINDOW is not triggering in Windows 11, but is working on Ubuntu and Artix. I don't believe it's the same as https://github.com/wxWidgets/Phoenix/issues/1960 because there is nothing triggering. I can't even get it to print to terminal. Here is exactly where I'm having the problem: https://github.com/ClayShoaf/wxPyMuPDF/blob/main/classes.py#L259

Code Example (click to expand) Unfortunately, I don't have my windows machine with me to write a new code snippet that will verifiably fail, but I can verify that this program does not trigger as expected on Windows 11: https://github.com/ClayShoaf/wxPyMuPDF/ You can run it with `python main.py sample.pdf`. On Linux, the buttons will display, as shown in the demo video, when you hover over a page, but on Windows 11, they will not. Adding `print("test")` to the beginning of the `showButtons` definition does not print, either.
da-dada commented 1 month ago

well, I can't find any problem what so ever with that event on Windows 11

ClayShoaf commented 1 month ago

@da-dada, is that to say that when you hover over the page, the buttons come up? Are you able to trigger a print command?

da-dada commented 1 month ago

@ClayShoaf, it's a mouse event and doesn't trigger anything (I'm afraid there is some suitable coding required)

ClayShoaf commented 1 month ago

In the code I posted, it is bound to self.showButtons which is a function that makes buttons appear on a page. Maybe "trigger" has some other meaning and I'm using the word incorrectly.

What I'm getting at is that, on linux, this event is recognized and the function runs. On Windows 11, it is not recognized. Even if I just put print("test") at the beginning of the self.showButtons function, it does not print.

da-dada commented 1 month ago

@ClayShoaf I'm not sure what your code is doing but the event is working absolutely perfect, as you can easily see by running this snippet



class Win(wx.Window):
    def __init__(self, parent, txt):
        super().__init__(parent)

        def evt_enter(_):
            self.SetBackgroundColour(wx.RED)
            self.Refresh()
            txt.SetLabel('Over Me')
        self.Bind(wx.EVT_ENTER_WINDOW, evt_enter)
        def evt_leave(_):
            self.SetBackgroundColour(wx.YELLOW)
            self.Refresh()
            txt.SetLabel('Not Over')
        self.Bind(wx.EVT_LEAVE_WINDOW, evt_leave)
        def evt_window(_):
            self.SetBackgroundColour(None)
            self.Refresh()
            txt.SetLabel("yes, I'm a Window..")
        self.Bind(wx.EVT_LEFT_DOWN, evt_window)

class Gui(wx.Frame):
    def __init__(self, parent):
        super().__init__(parent, title='enter / leave Window')

        vbox = wx.BoxSizer(wx.VERTICAL)
        txt = wx.StaticText(self)
        vbox.Add(txt, 0, wx.LEFT|wx.TOP|wx.EXPAND, 10)
        win = Win(self, txt)
        vbox.Add(win, 1, wx.LEFT|wx.TOP|wx.EXPAND, 10)
        self.SetSizer(vbox)
        self.SetBackgroundColour(None)
        def evt_text(_):
            txt.Refresh()
            txt.SetLabel("yes, I'm text..")
        txt.Bind(wx.EVT_LEFT_DOWN, evt_text)

        self.Show()

app = wx.App()
Gui(None)
app.MainLoop()
ClayShoaf commented 1 month ago

You have to use three ticks ``` at the top and bottom of your code to get it to format correctly.

da-dada commented 1 month ago

@ClayShoaf or if you like to keep the handlers separate from the window



class WinEvh:
    def __init__(self, gui):
        super().__init__(gui)
        self.gui = gui

    def evt_enter(self, _):
        self.SetBackgroundColour(wx.RED)
        self.Refresh()
        self.gui.txt.SetLabel('Over Me')
    def evt_leave(self, _):
        self.SetBackgroundColour(wx.YELLOW)
        self.Refresh()
        self.gui.txt.SetLabel('Not Over')
    def evt_window(self, _):
        self.SetBackgroundColour(None)
        self.Refresh()
        self.gui.txt.SetLabel("yes, I'm a Window..")

class Win(WinEvh, wx.Window):
    def __init__(self, gui):
        super().__init__(gui)

        self.Bind(wx.EVT_ENTER_WINDOW, self.evt_enter)
        self.Bind(wx.EVT_LEAVE_WINDOW, self.evt_leave)
        self.Bind(wx.EVT_LEFT_DOWN, self.evt_window)

class Gui(wx.Frame):
    def __init__(self, parent):
        super().__init__(parent, title='enter / leave Window')

        vbox = wx.BoxSizer(wx.VERTICAL)
        self.txt = wx.StaticText(self)
        vbox.Add(self.txt, 0, wx.LEFT|wx.TOP|wx.EXPAND, 10)
        win = Win(self)
        vbox.Add(win, 1, wx.LEFT|wx.TOP|wx.EXPAND, 10)
        self.SetSizer(vbox)
        self.SetBackgroundColour(None)
        def evt_text(_):
            self.txt.Refresh()
            self.txt.SetLabel("yes, I'm text..")
        self.txt.Bind(wx.EVT_LEFT_DOWN, evt_text)

        self.Show()

app = wx.App()
Gui(None)
app.MainLoop()
DietmarSchwertberger commented 1 month ago

@ClayShoaf : please provide a small runnable sample demonstrating the problem.

ClayShoaf commented 1 month ago

@DietmarSchwertberger this is as short as I can make it to reproduce the result. wx.EVT_ENTER_WINDOW is triggered on Linux, but not on Windows:

import wx
import wx.lib.scrolledpanel as scrolled

app = wx.App()
root = wx.Frame(None, -1, "wxPyMuPDF", size=(800,600))
root.Maximize(True)
vbox = wx.BoxSizer(wx.VERTICAL)
root.SetSizer(vbox)
root.SetAutoLayout(1)

scrolly = scrolled.ScrolledPanel(root, size=root.GetSize())
scrolly.SetAutoLayout(0)
gap = 10
layout = 1
hbox = wx.BoxSizer(wx.HORIZONTAL)
fgs = wx.FlexGridSizer(cols=1, vgap=10, hgap=10)
gbs = wx.GridBagSizer(gap, gap)
gbs.SetEmptyCellSize((-gap,-gap))

pages = [wx.Window(scrolly), wx.Window(scrolly)]

def test_output(e):
    print('AAAAAAAAAAAAAAAAAAAAAAAAAAAA')

for i in range(len(pages)):
    pages[i].SetBackgroundColour('white')
    test_img = wx.StaticBitmap(pages[i], -1, wx.Image('./test.png',wx.BITMAP_TYPE_PNG))
    pages[i].SetSize(test_img.GetSize())
    pages[i].Bind(wx.EVT_ENTER_WINDOW, test_output)
    gbs.Add(pages[i], (i,0), flag=wx.ALIGN_CENTER_HORIZONTAL)

hbox.AddStretchSpacer(1)
hbox.Add(gbs)
hbox.AddStretchSpacer(1)
scrolly.SetSizer(hbox)
scrolly.SetupScrolling()

vbox.Add(scrolly, 0, wx.ALIGN_CENTER)

def on_resize(e):
    scrolly.SetSize(root.GetVirtualSize())
    scrolly.Update()
    scrolly.SetupScrolling(scrollToTop=False)

root.Bind(wx.EVT_SIZE, on_resize)

root.Show()
app.MainLoop()

You should be able to use any test.png file for this.

It appears that the problem occurs when there is a wx.StaticBitmap on the window. It's like the mouse event doesn't "punch through" to the underlying window, because the bitmap is covering it. I have tried binding the wx.StaticBitmap to wx.EVT_ENTER_WINDOW. This, at least, will cause the event to trigger on windows, but then I can't get the buttons, in my main program, to show up on my windows machine. I don't know if they are also behind the bitmap, or what. I haven't had time to troubleshoot any further, and I don't want to waste my time if there's something obvious that I'm missing.

My fear, in posting this, is that this anomaly will be "fixed" by making it not work on Linux either.

da-dada commented 1 month ago

well, there is no punch through: the mouse can only be in one window what you could do is make the window under the bitmap a bit larger (what you may experience on your other systems) and the mouse will then go into that 'frame' of the underlying window first, trigger your handler and...

ClayShoaf commented 1 month ago

@da-dada Incorrect. The reason I had to write this logic:

        elif event.GetEventType() == wx.wxEVT_LEAVE_WINDOW:
            pos = event.GetPosition()
            rect_l = self.rot_l.GetRect()
            rect_r = self.rot_r.GetRect()
            rect_save = self.save_img.GetRect()
            good = True
            if rect_r.Contains(pos):
                good = False
            elif rect_l.Contains(pos):
                good = False
            elif rect_save.Contains(pos):
                good = False
            if good:
                self.rot_l.Hide()
                self.rot_r.Hide()
                self.save_img.Hide()
                self.parent.SetFocus()

is because whenever the mouse went over one of the buttons, it would trigger wx.EVT_LEAVE_WINDOW. This means that the mouse is still being recognized as being in the window, even though it's over a wx.StaticBitmap (on Linux).

Even if I were willing to do a work-around solution, like making the window bigger than the image it contains, it wouldn't work, because as soon as the mouse was over the image, it would trigger wx.EVT_LEAVE_WINDOW and my buttons would disappear again (on Windows).

infinity77 commented 1 month ago

I am not sure if things have changed since a long time ago, but if my memory serves me well, on GTK wx.StaticBitmap is not a real control. GTK draws the bitmap itself on the parent widget. This is not the case on Windows.

I am not entirely clear what your code does and why it is structured that way - and why you do need a background bitmap in the first place. But @da-dada is right, there cannot be a "punch-through" in this case.

I don't have a fix out of the blue, beside removing the use of wx.StaticBitmap and draw the images yourself inside a wx.EVT_PAINT event. Something along these lines:

import wx
import wx.lib.scrolledpanel as scrolled

class Dummy(wx.Window):

    def __init__(self, parent):

        wx.Window.__init__(self, parent)

        self.bitmap = wx.Bitmap('./test.png', wx.BITMAP_TYPE_PNG)
        self.SetInitialSize(self.DoGetBestSize())

        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def DoGetBestSize(self):

        w, h = self.bitmap.GetWidth(), self.bitmap.GetHeight()
        return wx.Size(w, h)

    def OnSize(self, event):

        event.Skip()
        self.Refresh()

    def OnPaint(self, event):

        w, h = self.GetClientSize()
        dc = wx.AutoBufferedPaintDC(self)
        dc.Clear()

        dc.DrawBitmap(self.bitmap, 0, 0)

app = wx.App()
root = wx.Frame(None, -1, "wxPyMuPDF", size=(800, 600))
root.Maximize(True)
vbox = wx.BoxSizer(wx.VERTICAL)
root.SetSizer(vbox)

scrolly = scrolled.ScrolledPanel(root, size=root.GetSize())
scrolly.SetAutoLayout(0)
gap = 10
layout = 1
hbox = wx.BoxSizer(wx.HORIZONTAL)
fgs = wx.FlexGridSizer(cols=1, vgap=10, hgap=10)
gbs = wx.GridBagSizer(gap, gap)
gbs.SetEmptyCellSize((-gap,-gap))

pages = [wx.Window(scrolly), wx.Window(scrolly)]

def test_output(e):
    print('AAAAAAAAAAAAAAAAAAAAAAAAAAAA')

for i in range(len(pages)):
    pages[i].SetBackgroundColour('white')
    test_win = Dummy(pages[i])
    pages[i].SetSize(test_win.GetSize())
    test_win.Bind(wx.EVT_ENTER_WINDOW, test_output)
    gbs.Add(pages[i], (i,0), flag=wx.ALIGN_CENTER_HORIZONTAL)

hbox.AddStretchSpacer(1)
hbox.Add(gbs)
hbox.AddStretchSpacer(1)
scrolly.SetSizer(hbox)
scrolly.SetupScrolling()

vbox.Add(scrolly, 0, wx.ALIGN_CENTER)

def on_resize(e):
    scrolly.SetSize(root.GetVirtualSize())
    scrolly.Update()
    scrolly.SetupScrolling(scrollToTop=False)

root.Bind(wx.EVT_SIZE, on_resize)

root.Show()
app.MainLoop()
ClayShoaf commented 1 month ago

on GTK wx.StaticBitmap is not a real control. GTK draws the bitmap itself on the parent widget. This is not the case on Windows.

Interesting.

I am not entirely clear what your code does and why it is structured that way - and why you do need a background bitmap in the first place.

Because I don't know all of the controls and features of wxPython, so I used the thing I was able to find that actually worked (or so I thought).

draw the images yourself inside a wx.EVT_PAINT event. Something along these lines:

Thank you very much for the code snippet! I haven't tested it on Windows, but I will assume it works as expected. I will work on changing my code to try implementing this way of drawing the images!

Now if I could just get the search function in the docs to work... 😅

DietmarSchwertberger commented 1 month ago

See here why unhandled mouse events do not propagate: https://docs.wxpython.org/events_overview.html#how-events-propagate-upwards

As Andrea wrote: paint yourself.

Have a look at the demo for some example code: Core Windows / Controls -> ScrolledWindow

As a general rule of thumb: use controls for controlling things. If you have something like a document viewer or editor, then you probably need to handle things yourself to be efficient. You don't want to have 1000 large StaticBitmaps for a 1000 page PDF document. Or: what happens if you want to zoom? You quickly will run out of memory.

ClayShoaf commented 1 month ago

what happens if you want to zoom?

I tried writing some logic to only do a high quality zoom for the pages that are on screen. I originally tried to resize the windows without doing a wx.IMAGE_QUALITY_NEAREST zoom on all of the pages, but I couldn't get it to work correctly, so, for now, I'm just doing the computationally cheapest zoom for all of the off screen pages and then doing a high quality zoom for the pages that are on screen.

I will look at the page you posted and explore wx.EVT_PAINT. I still haven't been able to wrap my head around exactly how a dc works, but I need to look into that as well.

I appreciate all of the replies!

da-dada commented 1 month ago

I appreciate all of the replies!

well, since your bug has run out of steam I think opening a discussion may attract more ideas