wxWidgets / Phoenix

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

ProgressDialog "skip" button doesn't always work #2482

Open cbeytas opened 11 months ago

cbeytas commented 11 months ago

wxPython v4.2.1, wxWidgets 3.2.2.1, Python 3.11 - Affects Windows10 and Linux/GTK3

The ProgressDialog returns its Cancel/Skip state on each call to Update(). You can also check the current state with the WasSkipped() method. proceed, skip = dialog.Update(percent)

Behavior 1: (Correct, as expected from the wxWidgets source msw/progdlg.cpp and generic/progdlg.cpp)

  1. skip returns False on calls to Update().
  2. When the Skip button is pressed WasSkipped() now returns True.
  3. On the next call to Update() skip returns True and the skip state gets reset so WasSkipped() now returns False.
  4. skip returns False again on further calls to Update() until the Skip button is pressed again.

Behavior 2: (Incorrect)

  1. skip always returns True on calls to Update().
  2. When the Skip button is pressed, WasSkipped() now returns True.
  3. WasSkipped() will continue to return True even after multiple calls Update() as it never gets reset.

Sometimes when stuck in behavior 2, it will randomly switch back to behavior 1.

The problem seems to be in the auto-generated file sip/cpp/sip_corewxProgressDialog.cpp.

bool skip;
sipRes = sipCpp->Update(value,*newmsg,&skip);
return sipBuildResult(0,"(bb)",sipRes,skip);

This passes a pointer to an uninitialized boolean value skip. Its value is indeterminate. A pointer to skip is passed to the sipCpp->Update method which inspects its value before passing the skip state. If, by chance, skip is initialized to False the method will set it to True and reset its internal state. If, by chance, skip is initialized to True, then the skip state will never get passed properly.

Code Example (click to expand) ```python import locale import time import wx class TestPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent) button = wx.Button(self, label="Show ProgressDialog") button.Bind(wx.EVT_BUTTON, self.OnButton) def OnButton(self, event): progress = wx.ProgressDialog("Skip Test", message="Attempt to skip me", style=wx.PD_APP_MODAL | wx.PD_AUTO_HIDE | wx.PD_CAN_SKIP) progress.Show() for num in range(100): proceed, skip = progress.Update(num) if skip is True: print("Skip==True") if progress.WasSkipped() is True: print("WasSkipped==True") break time.sleep(0.1) progress.Update(100) progress.Destroy() class TestFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, parent=None, title="ProgressDialog Skip Test") panel = TestPanel(self) self.Show() if __name__ == "__main__": app = wx.App() locale.setlocale(locale.LC_ALL, 'C') frame = TestFrame() app.MainLoop() ```
swt2c commented 11 months ago

Very interesting. I don't think the issue is quite as you stated - really, it seems that in the C++ API, skip is both an output and an input, and wxPython doesn't let you supply it as an input. This is a pretty strange C++ API.

cbeytas commented 11 months ago

That's the crux of the problem. The C++ API only sets skip to True if its current value is False. So the behavior depends on what skip is initialized to: msw/progdlg.cpp generic/progdlg.cpp I think the fix would be to initialize skip to False before making the call. That would guarantee behavior 1. It would also be good to add a note to the documentation that a call to Update() where skip returns True resets the skip state of the dialog and WasSkipped() will return False again.