I did find SetDoubleBuffered(True) in parent panel would fix the flickering problem(mainly from wx.StaticText), but it slows down the whole UI especially the ObjectListView element when you sort a column. So I searched a bit and according to https://wiki.wxwidgets.org/Flicker-Free_Drawing, get rid of background erasing for wx.StaticText should do the job:
class NoFlickeringTxtCtrl(wx.StaticText):
def __init__(self, parent, id=-1, label="", pos=wx.DefaultPosition,
size=wx.DefaultSize, style=0, name="staticText"):
wx.StaticText.__init__(self, parent, id, label, pos, size,
style, name)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)
def OnErase(self, evt):
evt.Skip()
But sadly it does not work, so I was wondering is there a better way to avoid flickering for wx.StaticText?
As far as I can tell, your code does not get rid of background erasing. For that to happen, you would have to replace evt.Skip() by pass.
If that also does not work, try to discard the EVT_ERASE_BACKGROUND for the parent panel of the StaticText (this helped me to get rid of flickering when painting a panel on EVT_PAINT).
Related
In my wxPython GUIs, the wx.BusyInfo widget no longer works. I'm working on OSX, and I recently upgraded to El Capitan.
This simple code below doesn't work anymore with either of the wx versions that I have available ('3.0.2.0' or '2.9.2.4'). As far as I can tell, wx.BusyInfo simply no longer shows up. Unfortunately, I don't know exactly when the widget stopped appearing.
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, size=(450, 350))
self.panel = wx.Panel(self)
btn = wx.Button(self.panel, wx.ID_ANY, "Do thing")
self.Bind(wx.EVT_BUTTON, self.do_thing)
self.Centre()
self.Show()
def do_thing(self, event):
wait = wx.BusyInfo('Please wait...')
time.sleep(5)
del wait
Any ideas on the cause or solution to this problem?
It looks like something may have changed with respect to when the paint events for the busy info window are processed. What you are seeing is simply that the paint event is not being delivered until after your sleep is done. If you give it a chance to be painted before you block with your busyness (such as calling wx.Yield(True) before) then you should see it working like with earlier versions of OSX. Better yet, if you can organize your busy task so it periodically yields then the system can do things like keep the busy info panel updated and show a real busy cursor instead of the spinning beachball.
I tested the suggested "Yield" workaround.
I also tried using "WindowDisabler", but that didn't work.
My band-aid fix was to refresh the wx.BusyInfo itself, instead of updating it.
For example (in Python):
busy=wx.BusyInfo("Loading corresponding data.")
#then do some work.
busy=wx.BusyInfo("Processing data for display.") #instead of busy.UpdateLabel("text")
#then do some different work.
#work done, time to let the BusyInfo object go.
del busy
I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/
Is it possible to make a wxPython window only re-sizable to a certain ratio? I know you can disable resizing; however, I'd like it so when the window was resized it stuck to a certain width to height ratio.
One obvious way to do this would be to bind wx.EVT_SIZE to a function that constrains the aspect ratio. I'm not certain this is The Right Way to do this, but it works:
import wx
class SizeEvent(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Centre()
self.SetSizeWH(400, 300)
self.Show(True)
def OnSize(self, event):
hsize = event.GetSize()[0] * 0.75
self.SetSizeHints(minW=-1, minH=hsize, maxH=hsize)
self.SetTitle(str(event.GetSize()))
app = wx.App()
SizeEvent(None, 1, 'sizeevent.py')
app.MainLoop()
(The boilerplate is borrowed from here.)
I'm not too familiar with wxPython, but can't you just reset the window size to your max/min size that you want once the user pass that? Preferably in the event that detects resizing?
import wx
class MainFrame(wx.Frame):
def __init__(self,parent,title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=DoubleBufferTest(self,-1)
self.Show(True)
class DoubleBufferTest(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id,style=wx.FULL_REPAINT_ON_RESIZE)
self.SetBackgroundColour("#FFFFFF")
self.timer = wx.Timer(self)
self.timer.Start(100)
self.Bind(wx.EVT_TIMER, self.update, self.timer)
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self,event):
event.Skip()
dc = wx.MemoryDC()
dc.SelectObject(wx.EmptyBitmap(640, 480))
gc = wx.GraphicsContext.Create(dc)
gc.PushState()
gc.SetBrush(wx.Brush("#CFCFCF"))
bgRect=gc.CreatePath()
bgRect.AddRectangle(0,0,640,480)
gc.FillPath(bgRect)
gc.PopState()
dc2=wx.PaintDC(self)
dc2.Blit(0,0,640,480,dc,0,0)
def update(self,event):
self.Refresh()
app = wx.App(False)
f=MainFrame(None,"Test")
app.MainLoop()
I've come up with this code to draw double buffered GraphicsContext content onto a panel, but there's a constant flickering across the window. I've tried different kinds of paths, like lines and curves but it's still there and I don't know what's causing it.
You get flicker because each Refresh() causes the background to get erased before calling onPaint. You need to bind to EVT_ERASE_BACKGROUND and make it a no-op.
class DoubleBufferTest(wx.Panel):
def __init__(self,parent=None,id=-1):
# ... existing code ...
self.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, event):
pass
# ... existing code ...
If you're using a relatively modern wxWidgets, you can use wx.BufferedPaintDC and avoid having to muck around with the memory DC and painting and blitting on your own. Also, on windows, FULL_REPAINT_ON_RESIZE often causes flickering even when you're not resizing the window due to funny things going on under the covers - if you don't need it, going with NO_FULL_REPAINT_ON_RESIZE may help. Otherwise, you'll want to simplify your code some to make sure you can get the simplest thing to work, and perhaps take a look at the DoubleBufferedDrawing wiki page at wxpython.org.
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
width=dc.GetSize()[0]
height=dc.GetSize()[1]
if height<width:
self.drawTestRects(dc)
else:
dc.Clear()
dc.EndDrawing()
def drawTestRects(self,dc):
dc.SetBrush(wx.Brush("#000000",style=wx.SOLID))
dc.DrawRectangle(50,50,50,50)
dc.DrawRectangle(100,100,100,100)
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
This code should draw the test rectangles only when the height is less than the width, and otherwise the window should remain clear. However, if you mess with resizing the window, the panel isn't actually redrawn unless it is moved off the window. What am I doing wrong?
You can bind a method to handle wx.EVT_SIZE or the panel and invalidate it there. Alternatively simply use the wx.FULL_REPAINT_ON_RESIZE for the panel.
The documentation for a SizeEvent claims that there may be some complications when drawing depends on the dimensions of the window. I do not know exactly what is going on behind the scenes. I followed the suggestion on the link and added the call self.Refresh() to the top of onPaint() and this seems to give the desired behavior. See mghie's answer for a more efficient example of working code.