Disabled button still catching clicks during long tasks wxpython - python

Disabled button still catch clicks during the long task. During the long tasks the button is grayed out but if you click it during the long task, click event fires after the long task has finished. e.g.
def onClick(self, evt):
self.btn.Disable()
for i in range (1000):
print i
self.btn.Enable()
Button disables itself before executing the long for loop, but if we click the button during for loop, it starts the for loop again, because it calls the onClick function again, after the for loop finishes.
Any idea how to disable the click event as well ?

Although I have my doubts as to whether you should be coding your long running event this way, you can achieve what you want by using Unbind on the button click, perform the long running task, using Yield to use up any subsequent button clicks and then at the end of the task Bind to the button again.
i.e.
import wx
import time
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.btn = wx.Button(self, -1, "Click Me")
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
self.Centre()
self.Show()
def onClick(self, event):
self.btn.Unbind(wx.EVT_BUTTON)
for i in range (10):
time.sleep(1)
print( i )
wx.GetApp().Yield() # Yielding allows button events to be used up
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
print ("Accepting clicks again")
if __name__ == "__main__":
app = wx.App()
ButtonFrame()
app.MainLoop()

To be honest I didn't really get what you are asking.
Your code works as follows:
When you click on the button, the button (i.e. self.btn) is disabled
It will stayed disabled and execute the for loop
Once done executing the for loop, the button goes back to live
If you would like to disable the button, you should do it outside of the onclick event.
For example:
self.btn.Disable() # This will grey out the button, you can't click it, so the following onClick function wouldn't be triggered
def onClick(self, evt):
# do something
If you would like to use the button to trigger a task execution, and disable the button that triggers the task when the task is in the middle of execution, the best way is to use multi-thread. You can take a look at the following two links for more information:
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
https://wiki.wxpython.org/LongRunningTasks

In fact it's easier than my first answer suggested. There is no reason to UnBind, simply using Yield before re-enabling the button will suffice:
import wx
import time
class ButtonFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,-1,"Disable Button Events")
panel = wx.Panel(self, -1)
self.btn = wx.Button(panel, -1, "Click Me", pos=(10,10), size=(80,30))
self.btn.Bind(wx.EVT_BUTTON, self.onClick)
self.Show()
def onClick(self, event):
self.btn.Disable()
for i in range (10):
time.sleep(1)
print("Long task running",i)
wx.GetApp().Yield() # Yielding allows button events to be used up
self.btn.Enable()
print("Accepting clicks again")
if __name__ == "__main__":
app = wx.App()
ButtonFrame()
app.MainLoop()

Related

Trying to create a dialog in another thread wxpython

I'm running a function in another thread that is supposed to fill out a dialog and then show it but it just seg faults as soon as I tried to alter the dialog in any way. I've read that this is a common issue with WxPython and that devs are not intended to directly alter dialogs in another thread.
How do I get around this? I can just call the function in my main thread but that will block my GUI and it is a lengthy operation to initialize the dialog - I would like to avoid this.
My code is similar to the below.
In the main thread
# Create the dialog and initialize it
thread.start_new_thread(self.init_dialog, (arg, arg, arg...))
The function I am calling
def init_dialog(self, arg, arg, arg....):
dialog = MyFrame(self, "Dialog")
# Setup the dialog
# ....
dialog.Show()
Even with a blank dialog and just a simple call to show inside the function I get a segmentation fault. Any help would be greatly appreciated, thanks.
I have made an applet to demonstrate keeping GUI responsive during calculations and calling the message box after the calculations.
import wx
import threading
import time
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "I am a test frame")
self.clickbtn = wx.Button(self, label="click me!")
self.Bind(wx.EVT_BUTTON, self.onClick)
def onClick(self, event):
self.clickbtn.Destroy()
self.status = wx.TextCtrl(self)
self.status.SetLabel("0")
print "GUI will be responsive during simulated calculations..."
thread = threading.Thread(target=self.runCalculation)
thread.start()
def runCalculation(self):
print "you can type in the GUI box during calculations"
for s in "1", "2", "3", "...":
time.sleep(1)
wx.CallAfter(self.status.AppendText, s)
wx.CallAfter(self.allDone)
def allDone(self):
self.status.SetLabel("all done")
dlg = wx.MessageDialog(self,
"This message shown only after calculation!",
"",
wx.OK)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_OK:
self.Destroy()
mySandbox = wx.App()
myFrame = TestFrame()
myFrame.Show()
mySandbox.MainLoop()
GUI stuff is kept in the main thread, while calculations continue unhindered. The results of the calculation are available at time of dialog creation, as you required.

wx.EVT_MAXIMIZE prevented exiting full screen

I have a frame with one radio box to toggle full screen. The frame is to go full screen when the user clicks the Maximize button. However, if I use the maximize button, the radio box would then fail to restore the window. If I use the radio box to go full screen, it will be able to restore the window.
import wx
class FSWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((800, 600))
self.RadioFullScreen = wx.RadioBox(self, -1, "Display", choices=["Windowed","Full Screen"])
self.RadioFullScreen.Bind(wx.EVT_RADIOBOX, self.FS)
self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
self.Sizer = None
self.Show()
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.ShowFullScreen(True)
else:
self.ShowFullScreen(False)
def OnMaximize(self, Event):
self.ShowFullScreen(True) # <-- Add self.Restore() or self.Maximize(False) here
self.RadioFullScreen.SetSelection(1)
App = wx.App()
frame =FSWindow(None, -1, "MainWindow")
App.MainLoop()
However, if I add self.Restore() or self.Maximize(False) before the self.ShowFullScreen(True) like I commented on the source code above, the radio buttons will work. Problem is, the window will be restored first before going full screen which is ugly. Any solution for this? Also please explain why this happened, if possible.
Running Python 2.7.9, WxPython 3.0.2 on Window 7 Professional 32-bit
It seems that ShowFullScreen is not setting some flag, so things get out of sync.
If I just use Maximize/Restore things work fine for me, i.e. following changes to your code.
def FS(self, Event):
if self.RadioFullScreen.GetSelection():
self.Maximize()
#self.ShowFullScreen(True, style=wx.FULLSCREEN_ALL)
print('done fs true')
else:
#self.ShowFullScreen(False, style=wx.FULLSCREEN_ALL)
self.Restore()
print('done fs false')
def OnMaximize(self, Event):
Event.Skip()
self.RadioFullScreen.SetSelection(1)
print('done max')
If you don't want the menu bar etc when the screen is maximized then uncomment the ShowFullScreen lines.
You are handling an event "Maximize", most of the time you want default behaviour also to happen, that is why I added Event.Skip to the OnMaximize handler - in this case it doesn't make a difference as it looks like the event is only fired after maximisation is already done.

How do I bind wx.CLOSE_BOX to a function?

Fairly simple question, but I can't seem to find the answer. I have a GUI which has a cancel button that asks the user to abort all unsaved changes when they press it. The GUI also has a wx.CLOSE_BOX, but this simply closes it because its not bound to my OnCancel function. How do I bind it?
Things I tried:
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), wx.CLOSE_BOX)
#This gives an AssertionError, replacing wx.EVT_CLOSE with wx.EVT_BUTTON also
# gives the same error
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
#This binds any time ```self.Close(True)``` occurs (which makes sense) but
# is not what I want. There are other buttons which close the GUI which should not
# use the OnCancel function
Thanks in advance for your help
EDIT: The code below should help clarify what I'm looking for
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, lambda event: self.OnCancel(event, newCRNum), cancelBtn)
def OnCancel(self, event, CRNum):
dlg = wx.MessageDialog(self, "Are you sure you want to cancel? All work will be lost and CR number will not be reserved.", "Cancel CR", wx.YES_NO|wx.NO_DEFAULT|wx.ICON_EXCLAMATION)
if dlg.ShowModal() == wx.ID_YES:
self.Destroy()
else:
dlg.Destroy
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
So what this does is create a whimsically large cancel button. When this button is pressed, a dialog box pops up and prompts the user if they really want to quit. If they say yes, the whole gui closes, if not, only the dialog box closes.
When the user presses the red (X) button in the top right of the GUI, I want the same thing to happen. Since is a button, I assume it can be bound to my OnCancel button, but how do I do this?
Reason for AssertionError: The third argument must be source widget like follow.
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum), self)
Try following example:
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
newCRNum = 0
self.Bind(wx.EVT_CLOSE, lambda event: self.OnCancel(event, newCRNum))
def OnCancel(self, event, num):
dlg = wx.MessageDialog(self, 'Do you want close?', 'Sure?',
wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event.Skip()
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()

check which button is pressed gtk3 using python

i have 10 button, which correspond to the same method. how am i going to check which button was clicked in the corresponding method? i tried to check for the button press of a particular button in the list by the following code, but i got segmentation fault error:
for i in range(0,10):
if button_list[i].clicked():
break
break
#operation with respect to the button clicked
Here's a sample code that illustrates knowing what button triggered the event by using the label of the button:
from gi.repository import Gtk
class ButtonWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Button Demo")
self.set_border_width(10)
hbox = Gtk.Box(spacing=6)
self.add(hbox)
#Lets create 10 buttons for this demo
#You could create and set the label for
#each of the buttons one by one
#but in this case we just create 10
#and call them Button0 to Button9
for i in range(10):
name = "Button{}".format(i)
button = Gtk.Button(name)
button.connect("clicked", self.on_button_clicked)
hbox.pack_start(button, True, True, 0)
def on_button_clicked(self, button):
print button.get_label()
def on_close_clicked(self, button):
print "Closing application"
Gtk.main_quit()
win = ButtonWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
So you could just check what the label is and act accordingly.
Once you have connected all the buttons to the same callback, I assume the callback will have this signature: callback(button) where button is the button that emitted the clicked signal.
Inside that callback should be easy to check which button was clicked using something like:
button_list.index(button)
This will return the index of the button inside your list.

wxPython, Disabling buttons?

I have a list of buttons and I have made a loop to find out what button is pressed, then disable that button on click.
Here is the snippet of code:
def change(self,event):
self.Disable()
for i in enumerate(file_pool):
self.button_pool.append(wx.Button(self.sizer, -1, i[1], pos=(20, i[0]*45),size=(200,40))) #this would create the list of buttons
for i in self.button_pool:
i.Bind(wx.EVT_BUTTON, self.change) #bind each button
However, this will Disable every widget, not just the pressed button. How can I only disable the clicked button?
Thanks
you can get your object from the event:
def change(self, event):
myobject = event.GetEventObject()
myobject.Disable()

Categories