multiline checkbox in wxpython - python

I'm working with wxpython (2.8) with python 2.5.
is it possible to force a wx.CheckBox to display its label on multiple
lines?
I'd like to be able to do the same as wx.StaticText.Wrap(width)
See the attached example: the wx.CheckBox is 200 px wide, but it's
label does not fit in this space.
Any help is really appreciated!
Thanks a lot
Mauro
#example starts here
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Hello World", size=
(300,200))
self.panel = wx.Panel(self, -1)
myVSizer = wx.BoxSizer(wx.VERTICAL)
#instantiating a checkbox 200 px wide. but the label is too
long
cb = wx.CheckBox(self.panel, -1, label="This is a very very
long label for 200 pixel wide cb!", size =wx.Size(200, -1))
myVSizer.Add( cb, 1)
self.panel.SetSizer(myVSizer)
myVSizer.Layout()
app = wx.App(redirect=True)
top = MyFrame()
top.Show()
app.MainLoop()

what about something like this? Flex!
(I've made it a radio button to show that it still behaves like one)
import wx
import textwrap
class MultilineRadioButton(wx.RadioButton):
def __init__(self, parent, id=-1, label=wx.EmptyString, wrap=10, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, validator=wx.DefaultValidator, name=wx.RadioButtonNameStr):
wx.RadioButton.__init__(self,parent,id,'',pos,size,style,validator,name)
self._label = label
self._wrap = wrap
lines = self._label.split('\n')
self._wrappedLabel = []
for line in lines:
self._wrappedLabel.extend(textwrap.wrap(line,self._wrap))
self._textHOffset = 20
dc = wx.ClientDC(self)
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
dc.SetFont(font)
maxWidth = 0
totalHeight = 0
lineHeight = 0
for line in self._wrappedLabel:
width, height = dc.GetTextExtent(line)
maxWidth = max(maxWidth,width)
lineHeight = height
totalHeight += lineHeight
self._textHeight = totalHeight
self.SetInitialSize(wx.Size(self._textHOffset + maxWidth,totalHeight))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.Draw(dc)
self.RefreshRect(wx.Rect(0,0,self._textHOffset,self.GetSize().height))
event.Skip()
def Draw(self, dc):
dc.Clear()
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
dc.SetFont(font)
height = self.GetSize().height
if height > self._textHeight:
offset = height / 2 - self._textHeight / 2
else:
offset = 0
for line in self._wrappedLabel:
width, height = dc.GetTextExtent(line)
dc.DrawText(line,self._textHOffset,offset)
offset += height
class HFrame(wx.Frame):
def __init__(self,pos=wx.DefaultPosition):
wx.Frame.__init__(self,None,title="Hello World",size=wx.Size(600,400),pos=pos)
self.panel = wx.Panel(self,-1)
sizer = wx.BoxSizer(wx.HORIZONTAL)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
self.panel.SetSizer(sizer)
sizer.Layout()
class VFrame(wx.Frame):
def __init__(self,pos=wx.DefaultPosition):
wx.Frame.__init__(self,None,title="Hello World",size=wx.Size(600,400),pos=pos)
self.panel = wx.Panel(self,-1)
sizer = wx.BoxSizer(wx.VERTICAL)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
cb = RadioButton(self.panel,-1,label="This is a very very long label for the control!",wrap=10)
sizer.Add(cb,1)
self.panel.SetSizer(sizer)
sizer.Layout()
app = wx.App(redirect=False)
htop = HFrame(pos=wx.Point(0,50))
htop.Show()
vtop = VFrame(pos=wx.Point(650,50))
vtop.Show()
app.MainLoop()

Instead of using checkbox with text, use a no label checkbox with static text for desired effect e.g.
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Hello World", size=(300,200))
self.panel = wx.Panel(self, -1)
myVSizer = wx.BoxSizer(wx.VERTICAL)
# use checkbox + static text to wrap the text
myHSizer = wx.BoxSizer(wx.HORIZONTAL)
cb = wx.CheckBox(self.panel, -1, label="")
label = wx.StaticText(self.panel, label="This is a very very long label for 100 pixel wide cb!", size=(100,-1))
label.Wrap(100)
myHSizer.Add(cb, border=5, flag=wx.ALL)
myHSizer.Add(label, border=5, flag=wx.ALL)
myVSizer.Add(myHSizer)
self.panel.SetSizer(myVSizer)
myVSizer.Layout()
app = wx.App(redirect=True)
top = MyFrame()
top.Show()
app.MainLoop()
this has added benefit that with different layouts you can make text centre to checkbox, or on left or right or any other place

Changing your label to
label="This is a very very\n long label for 200\n pixel wide cb!"
should do it.
That is, put in explicit \n characters.

Related

Creating a Drag and Drop Interface for Images in wxPython

Firstly, this is what I'm trying to make with wxPython. I would like the region labeled "Picture 1" to be able to accept a dragged image, and then when an image is dragged there, be replaced with a thumbnail of that image. I have researched "Drag and Drop" in wxPython, but I can't seem to find the tools needed for me to do this.
Any help getting me started on the right track would be greatly appreciated.
You will want the Pillow package for creating a thumbnail. The other piece you will want is most likely the wx.FileDropTarget class. I ended up doing the following:
import os
import wx
from PIL import Image
from wx.lib.pubsub import pub
PhotoMaxSize = 240
class DropTarget(wx.FileDropTarget):
def __init__(self, widget):
wx.FileDropTarget.__init__(self)
self.widget = widget
def OnDropFiles(self, x, y, filenames):
print(filenames)
image = Image.open(filenames[0])
image.thumbnail((PhotoMaxSize, PhotoMaxSize))
image.save('thumbnail.png')
pub.sendMessage('dnd', filepath='thumbnail.png')
return True
class PhotoCtrl(wx.App):
def __init__(self, redirect=False, filename=None):
wx.App.__init__(self, redirect, filename)
self.frame = wx.Frame(None, title='Photo Control')
self.panel = wx.Panel(self.frame)
pub.subscribe(self.update_image_on_dnd, 'dnd')
self.PhotoMaxSize = 240
self.createWidgets()
self.frame.Show()
def createWidgets(self):
instructions = 'Browse for an image'
img = wx.Image(240,240)
self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.Bitmap(img))
filedroptarget = DropTarget(self)
self.imageCtrl.SetDropTarget(filedroptarget)
instructLbl = wx.StaticText(self.panel, label=instructions)
self.photoTxt = wx.TextCtrl(self.panel, size=(200,-1))
browseBtn = wx.Button(self.panel, label='Browse')
browseBtn.Bind(wx.EVT_BUTTON, self.onBrowse)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
0, wx.ALL|wx.EXPAND, 5)
self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
self.sizer.Add(browseBtn, 0, wx.ALL, 5)
self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)
self.panel.SetSizer(self.mainSizer)
self.mainSizer.Fit(self.frame)
self.panel.Layout()
def onBrowse(self, event):
"""
Browse for file
"""
wildcard = "JPEG files (*.jpg)|*.jpg"
dialog = wx.FileDialog(None, "Choose a file",
wildcard=wildcard,
style=wx.OPEN)
if dialog.ShowModal() == wx.ID_OK:
self.photoTxt.SetValue(dialog.GetPath())
dialog.Destroy()
self.onView()
def update_image_on_dnd(self, filepath):
self.onView(filepath=filepath)
def onView(self, filepath=None):
if not filepath:
filepath = self.photoTxt.GetValue()
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.PhotoMaxSize
NewH = self.PhotoMaxSize * H / W
else:
NewH = self.PhotoMaxSize
NewW = self.PhotoMaxSize * W / H
img = img.Scale(NewW,NewH)
self.imageCtrl.SetBitmap(wx.Bitmap(img))
self.panel.Refresh()
if __name__ == '__main__':
app = PhotoCtrl()
app.MainLoop()
This worked for me on Windows 7 with wxPython 4. Note that I am dragging a file from Windows Explorer onto my wxPython application's image widget.

Change background color of a Canvas ScaledText

I'm using wxPython Canvas' AddScaledTextBox(...) to create a scaled Text Box.
See here for example:
Box = Canvas.AddScaledTextBox("A Two Line\nString",
Point,
2,
BackgroundColor = "Yellow",
LineColor = "Red",
LineStyle = "Solid",
PadSize = 5,
Family = wx.TELETYPE,
Position = 'bl')
How to change the Background color, later, after the textbox has been defined? (for example user input => background color change)
Pretty sure this is a bug. See lines 1841 - 1845 in the wxPython source - there is a reference to self.BackgroundColor but it's never used.
So for your example, you need to set the color of the brush associated with the text box. I've based the following off of Robin Dunn's example. The function SetBoxBackground is the important part.
As noted in the comments, just calling box.Brush.SetColour(color) or box.Pen.SetColour(color) can cause issues: if you have two text boxes with the same color, changing the brush/pen color for one will also affect the other. (I'm not 100% sure, but I think this is because of caching, see for example this comment in the source.)
import wx
from wx.lib.floatcanvas import FloatCanvas
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.canvas = FloatCanvas.FloatCanvas(self, BackgroundColor = "black")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
self.box = self.canvas.AddScaledTextBox("A Two Line\nString",
(0,10),
2,
BackgroundColor = "Yellow",
LineColor = "Red",
LineStyle = "Solid",
PadSize = 5,
Family = wx.TELETYPE,
Position = 'bl')
self.box2 = self.canvas.AddScaledTextBox("Second Box",
(0,0),
2,
BackgroundColor = "Yellow",
LineColor = "Red",
LineStyle = "Solid",
PadSize = 5,
Family = wx.TELETYPE,
Position = 'bl')
self.box.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnRectDown)
wx.CallAfter(self.canvas.ZoomToBB)
def OnRectDown(self, event):
print ('Rectangle: Left Button down clicked at:', event.HitCoords)
if self.box.Color == 'red':
self.SetBoxBackground('green', "Solid")
self.box.SetColor('black')
else:
self.SetBoxBackground('white', "Solid")
self.box.SetColor('red')
self.canvas.ClearBackground()
self.canvas.Refresh()
self.canvas.Draw(True)
def SetBoxBackground(self, color, style, linewidth=1):
# See https://github.com/wxWidgets/wxPython/blob/master/wx/lib/floatcanvas/FloatCanvas.py#L1841
# Create a new brush (fill) with specified color
self.box.SetBrush(color, style)
# Create a new pen (line) with specified color
self.box.SetPen(color, style, linewidth)
self.box.SetBackgroundColor(color) # Now this works correctly
app = wx.App(0)
frame = TestFrame(None, title="Test")
frame.Show(True)
app.MainLoop()
The ScaledTextBox class derives from TextObjectMixin which has SetColor and SetBackgroundColor methods.
Code:
import wx
from wx.lib.floatcanvas import FloatCanvas
class TestFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.canvas = FloatCanvas.FloatCanvas(self, BackgroundColor = "black")
MainSizer = wx.BoxSizer(wx.VERTICAL)
MainSizer.Add(self.canvas, 4, wx.EXPAND)
self.SetSizer(MainSizer)
self.A = self.canvas.AddScaledTextBox('yoda', (0,0), Position = 'tl', Alignment = 'center', PadSize = 10, Size = 15, Width = 150, BackgroundColor = 'white')
self.A.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnRectDown)
wx.CallAfter(self.canvas.ZoomToBB)
def OnRectDown(self, event):
print 'Rectangle: Left Button down clicked at:', event.HitCoords
if self.A.Color == 'red':
self.A.SetColor('black')
else:
self.A.SetColor('red')
self.canvas.Draw(True)
app = wx.App(0)
frame = TestFrame(None, title="Test")
frame.Show(True)
app.MainLoop()
EDIT:
Forced a redraw of the canvas after the color change. Also, changing the background color isn't sticking for some reason, so I switched to demoing changing the text color.
Removed secondary edit because it was just creating a new text box instead of updating the original.

WxPython's ScrolledWindow element collapses to minimum size

I am using a Panel within a Frame to display images (the GUI need to switch between multiple panels and hence the hierarchy). As images should be displayed in native size I used ScrolledWindow as the panel parent. The scrolls do appear and work, but it causes the Panel to collapse to minimum size and it needs to be resized using drag&drop every time.
Is there a way around this?
Below is a reduced version of the code, which shows the problem:
import os
import wx
from wx.lib.pubsub import pub
class Edit_Panel(wx.PyScrolledWindow):
def __init__(self, parent):
super(Edit_Panel, self).__init__(parent)
# Display size
width, height = wx.DisplaySize()
self.photoMaxSize = height - 500
# Loaded image
self.loaded_image = None
# Icons
self.open_icon_id = 500
# Generate panel
self.layout()
def layout(self):
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
divider = wx.StaticLine(self, -1, style = wx.LI_HORIZONTAL)
self.main_sizer.Add(divider, 0, wx.ALL | wx.EXPAND)
self.toolbar = self.init_toolbar()
self.main_sizer.Add(self.toolbar, 0, wx.ALL)
img = wx.EmptyImage(self.photoMaxSize, self.photoMaxSize)
self.image_control = wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img))
self.main_sizer.Add(self.image_control, 0, wx.ALL | wx.CENTER, 5)
self.image_label = wx.StaticText(self, -1, style = wx.ALIGN_CENTRE)
self.main_sizer.Add(self.image_label, 0, wx.ALL | wx.ALIGN_CENTRE, 5)
self.SetSizer(self.main_sizer)
fontsz = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT).GetPixelSize()
self.SetScrollRate(fontsz.x, fontsz.y)
self.EnableScrolling(True, True)
def init_toolbar(self):
toolbar = wx.ToolBar(self)
toolbar.SetToolBitmapSize((16, 16))
open_ico = wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16, 16))
open_tool = toolbar.AddSimpleTool(self.open_icon_id, open_ico, "Open", "Open an Image Directory")
handler = self.on_open_reference
self.Bind(event = wx.EVT_MENU, handler = handler, source = open_tool)
toolbar.Realize()
return toolbar
def on_open_reference(self, event, wildcard = None):
if wildcard is None:
wildcard = self.get_wildcard()
defaultDir = '~/'
dbox = wx.FileDialog(self, "Choose an image to display", defaultDir = defaultDir, wildcard = wildcard, style = wx.OPEN)
if dbox.ShowModal() == wx.ID_OK:
file_name = dbox.GetPath()
# load image
self.load_image(image = file_name)
dbox.Destroy()
def get_wildcard(self):
wildcard = 'Image files (*.jpg;*.png;*.bmp)|*.png;*.bmp;*.jpg;*.jpeg'
return wildcard
def load_image(self, image):
self.loaded_image = image
# Load image
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# Label image name
image_name = os.path.basename(image)
self.image_label.SetLabel(image_name)
# scale the image, preserving the aspect ratio
scale_image = True
if scale_image:
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.photoMaxSize
NewH = self.photoMaxSize * H / W
else:
NewH = self.photoMaxSize
NewW = self.photoMaxSize * W / H
img = img.Scale(NewW, NewH)
self.image_control.SetBitmap(wx.BitmapFromImage(img))
# Render
self.main_sizer.Layout()
self.main_sizer.Fit(self)
self.Refresh()
pub.sendMessage("resize", msg = "")
class Viewer_Frame(wx.Frame):
def __init__(self, parent, id, title):
super(Viewer_Frame, self).__init__(parent = parent, id = id, title = title)
# Edit panel
self.edit_panel = Edit_Panel(self)
# Default panel
self.main_panel = self.edit_panel
# Render frame
self.render_frame()
# Subscription to re-render
pub.subscribe(self.resize_frame, ("resize"))
def render_frame(self):
# Main Sizer
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
# Add default sizer
self.main_sizer.Add(self.main_panel, 1, wx.EXPAND)
# Render
self.SetSizer(self.main_sizer)
self.Show()
self.main_sizer.Fit(self)
self.Center()
def resize_frame(self, msg):
self.main_sizer.Fit(self)
if __name__ == "__main__":
app = wx.App(False)
frame = Viewer_Frame(parent = None, id = -1, title = 'Toolkit')
app.MainLoop()
You're calling Fit(), so you're explicitly asking the panel to fit its contents, but you don't specify the min/best size of this contents anywhere (AFAICS, there is a lot of code here, so I could be missing something).
If you want to use some minimal size for the panel, just set it using SetMinSize().

Modifying minimal width of left tab style of wx Python's agw LabelBook?

I'm trying to use wx Python's AGW LabelBook (using wxPython 2.8.11.0, Python 2.7.1+, Ubuntu 11.04), such that the tabs (list) are left-aligned; here I have some short texts, and I expected the tablist area would have its width shortened accordingly; but instead I get this:
At that mouse position, I get a sizer pointer - and I can drag it to the right to increase the width of the tablist area as much as I want; but I cannot drag it any further to the left, to make the width shorter. I also tried to use INB_FIT_LABELTEXT, but it doesn't seem to change anything...
Is it possible to somehow instruct LabelBook to set the minimal width of the left tablist area to the approx width of text (say, indicated at the drawn red line)?
This is the code I used to generate the screenshot:
import wx
import wx.lib.agw
import wx.lib.agw.labelbook as LB
from wx.lib.agw.fmresources import *
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Labelbook test")
self.tlbook = LB.LabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
self.tlbook_panel_1 = wx.Panel(self.tlbook)
self.tlbook_panel_2 = wx.Panel(self.tlbook)
self.tlbook.AddPage(self.tlbook_panel_1, "Test 1")
self.tlbook.AddPage(self.tlbook_panel_2, "Test 2")
sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.SetSize((450, 250))
self.Layout()
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Ok, I think I got it:
... and the thing is, that the width of the tab area is hardcoded in the source for LabelBook as 100 pixels, but not all in the same class - so some monkeypatching is required, if one wants to leave the source in tact. Here is the code:
import wx
import wx.lib.agw
import wx.lib.agw.labelbook as LB
#~ from wx.lib.agw.labelbook import * # for INB_BOLD_TAB_SELECTION = 16384? nope
INB_BOLD_TAB_SELECTION = 16384
from wx.lib.agw.fmresources import *
WIDTHLIMITPIX=20
class OLabelContainer(LB.LabelContainer): # overloaded version
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelContainer"):
super(OLabelContainer, self).__init__(parent, id, pos, size, style, agwStyle, name)
def Resize(self, event): # copy from wx/lib/agw/labelbook.py
# Resize our size
self._tabAreaSize = self.GetSize()
newWidth = self._tabAreaSize.x
x = event.GetX()
if self.HasAGWFlag(INB_BOTTOM) or self.HasAGWFlag(INB_RIGHT):
newWidth -= event.GetX()
else:
newWidth = x
# hack: was 100 here
if newWidth < WIDTHLIMITPIX: #100: # Dont allow width to be lower than that
newWidth = WIDTHLIMITPIX #100
self.SetSizeHints(newWidth, self._tabAreaSize.y)
# Update the tab new area width
self._nTabAreaWidth = newWidth
self.GetParent().Freeze()
self.GetParent().GetSizer().Layout()
self.GetParent().Thaw()
LB.LabelContainer = OLabelContainer # do monkeypatch old class
class MyLabelBook(LB.LabelBook):
def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, agwStyle=0, name="LabelBook"):
super(MyLabelBook, self).__init__(parent, id, pos, size, style, agwStyle, name)
self._fontSizeMultiple = 1.0
self._fontBold = False
print(self._pages) # is OLabelContainer, OK
def GetFontBold(self): # copy from wx/lib/agw/labelbook.py
return self._fontBold
def ResizeTabArea(self): # copy from wx/lib/agw/labelbook.py
agwStyle = self.GetAGWWindowStyleFlag()
if agwStyle & INB_FIT_LABELTEXT == 0:
return
if agwStyle & INB_LEFT or agwStyle & INB_RIGHT:
dc = wx.MemoryDC()
dc.SelectObject(wx.EmptyBitmap(1, 1))
font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
font.SetPointSize(font.GetPointSize()*self._fontSizeMultiple)
if self.GetFontBold() or agwStyle & INB_BOLD_TAB_SELECTION:
font.SetWeight(wx.FONTWEIGHT_BOLD)
dc.SetFont(font)
maxW = 0
for page in xrange(self.GetPageCount()):
caption = self._pages.GetPageText(page)
w, h = dc.GetTextExtent(caption)
maxW = max(maxW, w)
maxW += 24 #TODO this is 6*4 6 is nPadding from drawlabel
if not agwStyle & INB_SHOW_ONLY_TEXT:
maxW += self._pages._nImgSize * 2
maxW = max(maxW, WIDTHLIMITPIX) # hack: was 100 here
self._pages.SetSizeHints(maxW, -1)
self._pages._nTabAreaWidth = maxW
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Labelbook test")
self.tlbook = MyLabelBook(self, -1, size=(400, 200), style = wx.NB_LEFT, agwStyle = INB_LEFT | INB_FIT_LABELTEXT | INB_FIT_BUTTON | INB_SHOW_ONLY_TEXT | INB_USE_PIN_BUTTON)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
self.tlbook_panel_1 = wx.Panel(self.tlbook)
self.tlbook_panel_2 = wx.Panel(self.tlbook)
self.tlbook.AddPage(self.tlbook_panel_1, "Test 1")
self.tlbook.AddPage(self.tlbook_panel_2, "Test 2")
sizer_1.Add(self.tlbook, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.SetSize((450, 250))
self.Layout()
app = wx.App(0)
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()

wxPython ScrolledWindow too small

I have a panel to control editing of a matplotlib graph in a wxPython frame. The wxPython install was recently updated to 2.8.12.1 from 2.6.4.0 and it broke a few things, namely, the scroll panel no longer fills a block but instead stays at a minimum size. I'm just now picking this up from a year ago so I'm a bit rusty. Any help would be much appreciated!
Below is a stripped-down version of the code that can be run on its own and displays the problem. The ScrolledWindow should expand up to 400px. When I run it self.scroll.GetSize() returns (292, 257) but it clearly is not displaying at that size.
# testing scroll panel for PlotEditFrame
import wx
# spoof the necessary matplotlib objects
class FakePlot:
def __init__(self):
self.figure = FakeFigure()
def get_figure(self):
return self.figure
class FakeFigure:
def __init__(self):
self.axes = [FakeAxis() for i in range(0,2)]
class FakeAxis:
def __init__(self):
self.lines = [FakeLine(i) for i in range(0, 4)]
class FakeLine:
def __init__(self,i):
self.label = "line #%s"%i
def get_label(self):
return self.label
class PlotEditFrame(wx.Frame):
"""
This class holds the frame for plot editing tools
"""
def __init__(self, parent, plot):
"""Constructor for PlotEditFrame"""
wx.Frame.__init__(self, parent, -1, "Edit Plot")
self.parent = parent
self.plot = plot
self.figure = plot.get_figure()
self.advanced_options = None
self.scroll = wx.ScrolledWindow(self, -1)
self.InitControls()
def InitControls(self):
"""Create labels and controls based on the figure's attributes"""
# Get current axes labels
self.lineCtrls = [( wx.StaticText(self.scroll, -1, "Column:"),
wx.StaticText(self.scroll, -1, "Color:"),
wx.StaticText(self.scroll, -1, ""))]
for axis in self.figure.axes:
for line in axis.lines:
color = wx.Colour(255,0,0,0)
lineTxt = wx.TextCtrl(self.scroll, -1, line.get_label(), size=(175,-1))
lineColor = wx.TextCtrl(self.scroll, -1, "#%02x%02x%02x"%color.Get())
lineBtn = wx.Button(self.scroll, -1, size=(25,25))
lineBtn.SetBackgroundColour(color)
self.lineCtrls.append((lineTxt, lineColor, lineBtn))
# Place controls
boxSizer = wx.BoxSizer(wx.VERTICAL)
lineBox = wx.StaticBox(self, -1, "Lines")
lineBoxSizer = wx.StaticBoxSizer(lineBox, wx.VERTICAL)
lineSizer = wx.FlexGridSizer(rows=len(self.lineCtrls)+1, cols=4, vgap=3, hgap=3)
for ctrls in self.lineCtrls:
lineSizer.AddMany([(ctrls[0], 0, wx.ALIGN_LEFT | wx.EXPAND),
(ctrls[1], 0, wx.ALIGN_LEFT),
(ctrls[2], 0, wx.ALIGN_CENTER| wx.FIXED_MINSIZE),
((3,3), 0, wx.ALIGN_CENTER)])
lineSizer.AddGrowableCol(0)
# Set size
self.scroll.SetSizer(lineSizer)
width = self.scroll.GetBestSize().width
height = self.scroll.GetBestSize().height
if height > 400:
height = 400
width = width + 25 # button size
self.scroll.SetSize((width, height))
self.scroll.SetScrollbars(0, 1, 1,1)
print "set scrollbars at %s x %s"%(width, height)
lineBoxSizer.Add(self.scroll, 0, wx.EXPAND)
boxSizer.AddMany([ (lineBoxSizer, 0, wx.EXPAND) ])
self.SetSizer(boxSizer)
self.SetAutoLayout(1)
self.Fit()
height = self.GetSize().GetHeight()
self.SetSizeHints(minH=height, maxH=height,
minW=width, maxW=width*5)
if __name__ == '__main__':
app = wx.PySimpleApp(0)
parent = wx.Frame(None, wx.ID_ANY, 'test', size=(300,300))
plot = FakePlot()
panel = PlotEditFrame(parent, plot)
panel.Show()
app.MainLoop()
I can't figure out what panel needs resized. Some things I've tried, to no avail:
# These have no visible effect
boxSizer.SetMinSize((width, height))
self.scroll.SetVirtualSize((width, height))
lineBoxSizer.Fit(self.scroll)
lineBoxSizer.SetVirtualSizeHints(self.scroll)
# This makes the window the right size, but not the scroll panel
lineBoxSizer.SetMinSize((width, height))
I edited your code a bit to get it to work:
import wx
# spoof the necessary matplotlib objects
class FakePlot:
def __init__(self):
self.figure = FakeFigure()
def get_figure(self):
return self.figure
class FakeFigure:
def __init__(self):
self.axes = [FakeAxis() for i in range(0,2)]
class FakeAxis:
def __init__(self):
self.lines = [FakeLine(i) for i in range(0, 4)]
class FakeLine:
def __init__(self,i):
self.label = "line #%s"%i
def get_label(self):
return self.label
class PlotEditFrame(wx.Frame):
"""
This class holds the frame for plot editing tools
"""
def __init__(self, parent, plot, size):
"""Constructor for PlotEditFrame"""
wx.Frame.__init__(self, parent, -1, "Edit Plot", size=size)
self.parent = parent
self.plot = plot
self.figure = plot.get_figure()
self.advanced_options = None
self.scroll = wx.ScrolledWindow(self, -1)
self.InitControls()
def InitControls(self):
"""Create labels and controls based on the figure's attributes"""
# Get current axes labels
self.lineCtrls = [( wx.StaticText(self.scroll, -1, "Column:"),
wx.StaticText(self.scroll, -1, "Color:"),
wx.StaticText(self.scroll, -1, ""))]
for axis in self.figure.axes:
for line in axis.lines:
color = wx.Colour(255,0,0,0)
lineTxt = wx.TextCtrl(self.scroll, -1, line.get_label(), size=(175,-1))
lineColor = wx.TextCtrl(self.scroll, -1, "#%02x%02x%02x"%color.Get())
lineBtn = wx.Button(self.scroll, -1, size=(25,25))
lineBtn.SetBackgroundColour(color)
self.lineCtrls.append((lineTxt, lineColor, lineBtn))
# Place controls
boxSizer = wx.BoxSizer(wx.VERTICAL)
lineBox = wx.StaticBox(self, -1, "Lines")
lineBoxSizer = wx.StaticBoxSizer(lineBox, wx.VERTICAL)
lineSizer = wx.FlexGridSizer(rows=len(self.lineCtrls)+1, cols=4, vgap=3, hgap=3)
for ctrls in self.lineCtrls:
lineSizer.AddMany([(ctrls[0], 0, wx.ALIGN_LEFT | wx.EXPAND),
(ctrls[1], 0, wx.ALIGN_LEFT),
(ctrls[2], 0, wx.ALIGN_CENTER| wx.FIXED_MINSIZE),
((3,3), 0, wx.ALIGN_CENTER)])
lineSizer.AddGrowableCol(0)
# Set size
self.scroll.SetSizer(lineSizer)
width = self.scroll.GetBestSize().width
height = self.scroll.GetBestSize().height
if height > 400:
height = 400
width = width + 25 # button size
self.scroll.SetSize((width, height))
self.scroll.SetScrollbars(0, 1, 1,1)
print "set scrollbars at %s x %s"%(width, height)
lineBoxSizer.Add(self.scroll, 1, wx.EXPAND)
boxSizer.Add(lineBoxSizer, 1, wx.EXPAND)
self.SetSizer(boxSizer)
self.SetAutoLayout(1)
#self.Fit()
height = self.GetSize().GetHeight()
self.SetSizeHints(minH=height, maxH=height,
minW=width, maxW=width*5)
if __name__ == '__main__':
app = wx.App(False)
plot = FakePlot()
frame = PlotEditFrame(None, plot, size=(300,300))
frame.Show()
app.MainLoop()
The main thing was to set the proportion to "1" on the following two lines:
lineBoxSizer.Add(self.scroll, 1, wx.EXPAND)
boxSizer.Add(lineBoxSizer, 1, wx.EXPAND)
I changed the way you start the program as it's a little silly to put a frame inside another frame for this case. Also PySimpleApp is deprecated, so I changed that too. I have almost never found a good use for the "Fit()" method, so I took that out as it was squashing the initial GUI too much.
Hope that helps!

Categories

Resources