GetSubBitmap() return empty bitmap on windows7 - python

I'm using wx.Bitmap's GetSubBitmap() API. On linux (ubuntu, fedora) everything OK. Trying to port code to windows7, GetSubBitmap() return empty bitmap, which result in black bitmap.
Here is simple example that draws periodically green rectangle somewhere on panel. On windows7 it turn to black rectangle.
import wx
import random
class mypanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.buffer = wx.EmptyBitmap(700, 500)
self.SetBackgroundColour("BLUE")
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.ID_TIMER = 1
self.timer = wx.Timer(self, self.ID_TIMER)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=self.ID_TIMER)
self.timer.Start(1000, wx.TIMER_CONTINUOUS)
self.BufferPaint()
self.Refresh(False)
def OnTimer(self, event):
print "OnTimerEvent " + str(event.Id)
self.BufferPaint()
def BufferPaint(self):
dc = wx.MemoryDC()
dc.SelectObject(self.buffer)
dc.SetBackground(wx.Brush("green"))
dc.Clear()
sub = self.buffer.GetSubBitmap(wx.Rect(5,5,30,30))
dc.SetBackground(wx.Brush("red"))
dc.Clear()
dc.DrawBitmap(sub, random.randint(0,600), random.randint(0,400))
self.Refresh(False)
def OnPaint(self, event=None):
dc = wx.BufferedPaintDC(self, self.buffer)
class myframe(wx.Frame):
"""Draw a line to a panel."""
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Draw on Panel", size=(800,600))
self.panel1 = mypanel(self)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel1, 2, wx.EXPAND)
self.SetSizer(self.sizer)
app = wx.App(False)
frame = myframe(None)
frame.Show()
app.MainLoop()

def BufferPaint(self):
dc = wx.MemoryDC()
dc.SelectObject(self.buffer)
dc.SetBackground(wx.Brush("red"))
dc.Clear()
dc.SetBrush(wx.Brush("green"))
dc.DrawRectangleRect( wx.Rect(random.randint(0,600),random.randint(0,400),30,30) )
self.Refresh(False)
I modified BufferPaint in your sample code to use DrawRectangleRect so that we explicitly paint with "green". Your code may be relying on default behavior how GetSubBitmap inherit the color which may be platform dependent.
My edit worked on my win7 machine.

Related

Scrollbar disappears / refresh not working in wx.python

In the following example the scrollbar disappears after using the calculating button, although the layout function was called. If you manually resize the frame it reappears. This behavior occurs only under windows, in linux the scrollbar functions as it should.
To fix it I tried the functions refresh() and update() (in layout funtion of class GUI_Diagrams_GHL) - but it didn't help.
I tried to reduce my application to this minmal working example:
# -*- coding: utf-8 -*-
import wx
from wx.lib.pubsub import pub
import wx.lib.scrolledpanel as scrolled
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas
class MainWindow(wx.Frame):
'''Frame that contains pretty much everything'''
def __init__(self,*args,**kwargs):
'''Constructor'''
super(MainWindow,self).__init__(*args,**kwargs)
self.panel = wx.Panel(self)
notebook = Notebook(self.panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(notebook,1, wx.ALL|wx.EXPAND,4)
self.panel.SetSizerAndFit(sizer)
self.panel.Layout()
class Notebook(wx.Notebook):
def __init__(self, parent):
wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style = wx.BK_DEFAULT)
tabTwo = GUI_Input_GHL(self)
self.AddPage(tabTwo, 'Input')
tabThree = GUI_Diagrams_GHL(self)
self.AddPage(tabThree, 'Diagrams')
class GUI_Input_GHL(scrolled.ScrolledPanel):
"""This panel contains the input fields for basic data."""
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent=parent, id=wx.ID_ANY)
self.label_1 = wx.StaticText(self,-1,label=u'Label 1')
self.button2 = wx.Button(self,-1,label=u'Start')
self.Bind(wx.EVT_BUTTON, self.StartCalc, self.button2)
self.layout()
def layout(self):
sizer = wx.GridBagSizer()
sizer.Add(self.button2, (8,0),(2,3), flag =wx.EXPAND)
sizer.Add(self.label_1, (0,0),flag=wx.ALIGN_CENTER_VERTICAL)
self.SetAutoLayout(1)
self.SetupScrolling()
self.SetSizerAndFit(sizer)
def StartCalc(self,event):
pub.sendMessage('GUI_Diagrams_Listener', message = 'test')
class GUI_Diagrams_GHL(scrolled.ScrolledPanel):
"""This panel contains diagrams"""
def __init__(self, parent):
scrolled.ScrolledPanel.__init__(self, parent=parent, id=wx.ID_ANY)
self.parent = parent
self.fig1 = Figure()
self.fig6 = Figure()
self.canvas1 = FigCanvas(self,-1,self.fig1)
self.axes1 = self.fig1.add_subplot(111)
self.canvas6 = FigCanvas(self,-1,self.fig6)
self.axes6 = self.fig6.add_subplot(111)
self.dia_R_hat_SetValues('test')
self.dia_theta_SetValues('test')
self.layout()
pub.subscribe(self.diagrams_SetValues, "GUI_Diagrams_Listener")
def layout(self):
sizer = wx.GridBagSizer()
sizer.Add(self.canvas1, (1,0), (12,12), wx.EXPAND)
sizer.Add(self.canvas6, (53,0), (12,12), wx.EXPAND)
## I guess here is the problem somewhere:
self.SetSizerAndFit(sizer)
self.SetAutoLayout(1)
self.SetupScrolling()
#self.Fit()
#self.Layout()
#self.FitInside()
#self.AlwaysShowScrollbars(True,True)
#self.Refresh()
#self.Update()
#self.parent.SetSize(self.parent.GetSize())
def diagrams_SetValues(self, message):
self.Output = message
self.dia_R_hat_SetValues(message)
self.dia_theta_SetValues(message)
self.layout()
def dia_R_hat_SetValues(self, Output):
self.axes1.clear()
self.axes1.plot(range(15),range(15), 'r-', linewidth = 2)
self.canvas1.draw()
def dia_theta_SetValues(self, Output):
self.axes6.clear()
self.axes6.plot(range(5),'k')
self.axes6.set_title(r"Absolute Temperature")
self.canvas6.draw()
if __name__ == '__main__':
app = wx.App()
frame = MainWindow(None, -1, 'MyApp')
frame.Show()
app.MainLoop()
I've figured it out myself :)
If you call the layout function of the main panel ('MainWindow.panel.Layout()') after updating the diagrams/in the layout of the diagram class, the whole panel refreshes and
the Scrollbars reappear. So a 'parent.parent.Layout()' worked for me in this case.
Any other suggestions/solutions?

Setting a background image in wxpython

I am having some confusing behavior in wxpython. I just loaded the newest version (3.0.0.0) into a RHEL 6.4 with Python 2.6.6.
Most things seem to work, but my previous code for running a gui with a background image fails. All the buttons work, etc, but the background is just the default gray one.
I tried tracking down the problem by running this minimal example from Mike Driscoll's python website (http://www.blog.pythonlibrary.org/2010/03/18/wxpython-putting-a-background-image-on-a-panel/), and had the same problem with no background showing up other than the default gray one (I previously used his example on another machine with an older version of wxpython [2.8.12.1] with no problems):
import wx
########################################################################
class MainPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.frame = parent
sizer = wx.BoxSizer(wx.VERTICAL)
hSizer = wx.BoxSizer(wx.HORIZONTAL)
for num in range(4):
label = "Button %s" % num
btn = wx.Button(self, label=label)
sizer.Add(btn, 0, wx.ALL, 5)
hSizer.Add((1,1), 1, wx.EXPAND)
hSizer.Add(sizer, 0, wx.TOP, 100)
hSizer.Add((1,1), 0, wx.ALL, 75)
self.SetSizer(hSizer)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
#----------------------------------------------------------------------
def OnEraseBackground(self, evt):
"""
Add a picture to the background
"""
# yanked from ColourDB.py
dc = evt.GetDC()
if not dc:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
bmp = wx.Bitmap("butterfly.jpg")
dc.DrawBitmap(bmp, 0, 0)
########################################################################
class MainFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, size=(600,450))
panel = MainPanel(self)
self.Center()
########################################################################
class Main(wx.App):
""""""
#----------------------------------------------------------------------
def __init__(self, redirect=False, filename=None):
"""Constructor"""
wx.App.__init__(self, redirect, filename)
dlg = MainFrame()
dlg.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = Main()
app.MainLoop()
Now, when I run this, I don't get any errors, but I determined that the function onEraseBackground never seems to run. I don't know what is going wrong here; did wxpython 3.0.0.0 stop allowing this kind of image background setting procedure?
Try commenting out the
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
line. Some other people have experienced the same issue and this line was the culprit - it prevents the EVT_ERASE_BACKGROUND event being triggered.
You can also try replacing this line with
self.SetBackgroundStyle(wx.BG_STYLE_ERASE)
to ensure the erase events are triggered.

How to use Refresh with a wx.ColourDialog in wxpython?

i am trying to Refresh() a panel which uses the wx.ColourDialog. Once I refresh the panel once, it is unable to refresh again. Try the following to see the problem in action.
By clicking the button, it will ask you what color you would like to change the rectangle to. Once you press OK, it should change the rectangles color. It will not work it will not change the rectangle.
import wx
xcolor_of_font_dia=(0,0,0)
class MyFrame(wx.Frame):
"""a frame with a panel"""
def __init__(self, parent=None, id=wx.ID_ANY, title=None):
global xcolor_of_font_dia
global dc
wx.Frame.__init__(self, parent, wx.ID_ANY, title)
self.panel = wx.Panel(self, size=(350, 200))
self.panel.Bind(wx.EVT_PAINT, self.on_paint)
self.button2 = wx.Button(self.panel, id=wx.ID_ANY, label='Button2',pos=(8, 38), size=(175, 28))
self.button2.Bind(wx.EVT_BUTTON, self.onColorDlg)
self.Fit()
def onColorDlg(self, event):
global xcolor_of_font_dia
global dc
"""
This is mostly from the wxPython Demo!
"""
dlg = wx.ColourDialog(self)
# Ensure the full colour dialog is displayed,
# not the abbreviated version.
dlg.GetColourData().SetChooseFull(True)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetColourData()
print 'You selected: %s\n' % str(data.GetColour().Get())
xcolor_of_font_dia='#%02x%02x%02x' % data.GetColour().Get()
dlg.Destroy()
self.panel.Refresh()
def on_paint(self, event):
global xcolor_of_font_dia
global dc
dc = wx.PaintDC(self.panel)
dc.SetPen(wx.Pen(xcolor_of_font_dia, 1))
rect = wx.Rect(50, 50, 100, 100)
dc.DrawRoundedRectangleRect(rect, 8)
# test it ...
app = wx.PySimpleApp()
frame1 = MyFrame(title='rounded-rectangle & circle')
frame1.Center()
frame1.Show()
app.MainLoop()
I cleaned your code a bit. Basically your globals were producing some problems as you were creating (and deleting) different dc instances after every size event.
You should not use globals if it is not strictly necessary (rarely is).
This works:
import wx
class MyFrame(wx.Frame):
"""a frame with a panel"""
def __init__(self, parent=None, id=wx.ID_ANY, title=None):
wx.Frame.__init__(self, parent, wx.ID_ANY, title)
self.xcolor = (0, 0, 0)
self.panel = wx.Panel(self, size=(350, 200))
self.panel.Bind(wx.EVT_PAINT, self.on_paint)
self.button2 = wx.Button(self.panel, id=wx.ID_ANY, label='Button2',
pos=(8, 38), size=(175, 28))
self.button2.Bind(wx.EVT_BUTTON, self.onColorDlg)
self.Fit()
def onColorDlg(self, event):
"""
This is mostly from the wxPython Demo!
"""
dlg = wx.ColourDialog(None)
dlg.GetColourData().SetChooseFull(True)
if dlg.ShowModal() == wx.ID_OK:
data = dlg.GetColourData()
self.xcolor = data.GetColour().Get()
print 'You selected: %s\n' % str(self.xcolor)
dlg.Destroy()
self.panel.Refresh()
def on_paint(self, event):
dc = wx.PaintDC(self.panel)
dc.SetPen(wx.Pen(self.xcolor, 2))
rect = wx.Rect(50, 50, 100, 100)
dc.DrawRoundedRectangleRect(rect, 8)
# test it ...
app = wx.PySimpleApp()
frame1 = MyFrame(title='rounded-rectangle & circle')
frame1.Center()
frame1.Show()
app.MainLoop()

How to make a canvas (rectangle) in wxpython?

I am trying to make a windows canvas type rectangle, here is an image if you are having issues understanding what i mean,
Is there any way todo this in wxpython? ( also, is there a way to set it to automatically adjust to the window width -20px?, so a radius around the window, and will adjust to the users window size.
EDIT: I asked on the wxPython IRC channel and a fellow named "r4z" came up with the following edit to my code which worked for me on Windows 7.
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.Bind(wx.EVT_PAINT, self.OnPaint)
#----------------------------------------------------------------------
def OnPaint(self, event):
""""""
pdc = wx.PaintDC(self)
try:
dc = wx.GCDC(pdc)
except:
dc = pdc
w, h = self.GetSizeTuple()
w = w - 10
h = h - 10
dc.Clear()
dc.DrawRectangle(x=5, y=5, width=w, height=h)
#----------------------------------------------------------------------
def OnSize(event):
event.EventObject.Refresh()
event.Skip()
if __name__ == "__main__":
app = wx.App(False)
frame = wx.Frame(None, title="Test")
panel = MyPanel(frame)
frame.Bind(wx.EVT_SIZE, OnSize)
frame.Show()
app.MainLoop()
Alternately, you might look at the wx.StaticBox widget.
EDIT #2: You could also just set the frame's style like this and skip the whole OnSize business:
frame = wx.Frame(None, title="Test", style=wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE)

Flicker-free drawable ScrolledWindow

I'm trying to build a ScrolledWindow that you can draw on using the mouse, and it's working too, but I'm getting a nasty flicker when the user is drawing on the window while the scrollbars aren't in the "home" position..
To reproduce, run the attached program, scroll a bit down (or to the right) and "doodle" a bit by keeping the left mouse button pressed. You should see a flickering now and then..
import wx
class MainFrame(wx.Frame):
""" Just a frame with a DrawPane """
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
s = wx.BoxSizer(wx.VERTICAL)
s.Add(DrawPane(self), 1, wx.EXPAND)
self.SetSizer(s)
########################################################################
class DrawPane(wx.PyScrolledWindow):
""" A PyScrolledWindow with a 1000x1000 drawable area """
VSIZE = (1000, 1000)
def __init__(self, *args, **kw):
wx.PyScrolledWindow.__init__(self, *args, **kw)
self.SetScrollbars(10, 10, 100, 100)
self.prepare_buffer()
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.Bind(wx.EVT_MOTION, self.on_motion)
def prepare_buffer(self):
self.buffer = wx.EmptyBitmap(*DrawPane.VSIZE)
dc = wx.BufferedDC(None, self.buffer)
dc.Clear()
dc.DrawLine(0, 0, 999, 999) # Draw something to better show the flicker problem
def on_paint(self, evt):
dc = wx.BufferedPaintDC(self, self.buffer, wx.BUFFER_VIRTUAL_AREA)
def on_mouse_down(self, evt):
self.mouse_pos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
def on_motion(self, evt):
if evt.Dragging() and evt.LeftIsDown():
dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
newpos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
coords = self.mouse_pos + newpos
dc.DrawLine(*coords)
self.mouse_pos = newpos
self.Refresh()
if __name__ == "__main__":
app = wx.PySimpleApp()
wx.InitAllImageHandlers()
MainFrame(None).Show()
app.MainLoop()
I tried using SetBackgroundStyle(wx.BG_STYLE_CUSTOM), or binding EVT_ERASE_BACKGROUND, or using RefreshRect instead of Refresh, but the flicker is still there.. Any idea on what I might try next?
My environment: Xubuntu 9.04, wxPython 2.8.9.1
(but tested on Ubuntu 10.04 too)
Many thanks for your time!
From Robin Dunn himself:
First, a Refresh() by default will
erase the background before sending
the paint event (although setting the
BG style or catching the erase event
would have taken care of that.) The
second and probably most visible
problem in this case is that in your
on_motion handler you are not
offsetting the ClientDC by the scroll
offsets, just the position in the
buffer that you are drawing the line
segment at. So when the buffer is
flushed out to the client DC it is
drawn at the physical (0,0), not the
virtual (0,0). In other words, the
flicker you are seeing is coming from
drawing the buffer at the wrong
position after every mouse drag event,
and then it immediately being drawn
again at the right position in the
on_paint triggered by the
Refresh().
You should be able to fix this by
calling PrepareDC on the client DC
before using it, like this:
cdc = wx.CLientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
However since you are doing a
Refresh or RefreshRect anyway,
there is no need to use a client DC
here at all, just let the flushing of
the buffer to the screen be done in
on_paint instead:
dc = wx.BufferedDC(None, self.buffer)
Using Joril recomendations and removing Refresh(), there is no flicker anymore (even enlarging the frame).
import wx
class MainFrame(wx.Frame):
""" Just a frame with a DrawPane """
def __init__(self, *args, **kw):
wx.Frame.__init__(self, *args, **kw)
s = wx.BoxSizer(wx.VERTICAL)
s.Add(DrawPane(self), 1, wx.EXPAND)
self.SetSizer(s)
########################################################################
class DrawPane(wx.PyScrolledWindow):
""" A PyScrolledWindow with a 1000x1000 drawable area """
VSIZE = (1000, 1000)
def __init__(self, *args, **kw):
wx.PyScrolledWindow.__init__(self, *args, **kw)
self.SetScrollbars(10, 10, 100, 100)
self.prepare_buffer()
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
self.Bind(wx.EVT_MOTION, self.on_motion)
def prepare_buffer(self):
self.buffer = wx.EmptyBitmap(*DrawPane.VSIZE)
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
dc.Clear()
dc.DrawLine(0, 0, 999, 999) # Draw something to better show the flicker problem
def on_paint(self, evt):
dc = wx.BufferedPaintDC(self, self.buffer, wx.BUFFER_VIRTUAL_AREA)
def on_mouse_down(self, evt):
self.mouse_pos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
def on_motion(self, evt):
if evt.Dragging() and evt.LeftIsDown():
newpos = self.CalcUnscrolledPosition(evt.GetPosition()).Get()
coords = self.mouse_pos + newpos
cdc = wx.ClientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)
dc.DrawLine(*coords)
self.mouse_pos = newpos
if __name__ == "__main__":
app = wx.PySimpleApp()
wx.InitAllImageHandlers()
MainFrame(None).Show()
app.MainLoop()

Categories