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!
Related
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.
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().
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()
Level: Beginner
I am using python v2.7 and wxPython v3.0 and the OS is windows 7.
My GUI app: Well in my GUI app I am reading some values from a server and then based upon number of these values I create panels in my GUI. Then each of these panels will represent the values in form of a staticText. For eg: If I receive from the server 1,2,3 values, then I create 3 panels each displaying 1, 2 and 3 respectively. This works fine till here.
Problem: I want to check the server every 5 seconds to fetch the values and accordingly update my GUI ie. I have to update the staticText on the panels to display the updated values. I don't want to add new panels I just want to update the values in the old panels.
For eg: If I check the server and if the server returns 1, 2, 3 as values then I want to create 3 panels displaying 1, 2, 3 values respectively. Then after 5 seconds when I check the server again then if server gives 4, 5, 6 as values then I just want to update these values on the old panels. Which means now the panels will display 4, 5, 6 instead of 1, 2, 3 respectively. I read some tutorials & posts on SC regarding using threads, I understood some basic facts too. Unfortunately I don't understand how to apply this concept to my problem.
It would be really great if I could get a working example for my this particular problem so that I could apply the same to my rest of the application.
Code: I have created a short sample code for this particular problem. The getLabels() in the class labelsA and the getLabels() in the class labelsB mimics the server by just generating some random values and returning them in a list. Then the list of values returned by getLabels() of the class labelA and the list of values returned by getLabels() of the class labelsB are used by createPanels()A and createPanelsB() respectively to create panels and to display these values. The panel in white background is panelA and the panel with yellow background is the panelB. It would be really great if some one can teach me how to use threading to update the values of these two panels without freezing/blocking my GUI.
Download: The sample code is provided below and can be downloaded from here too to avoid identation problems.
#!/usr/bin/env python
from random import randrange
import wx
import wx.lib.scrolledpanel
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 800
screenHeight = 450
screenSize = (screenWidth, screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.locationFont = locationFont = wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD)
mainSizer = wx.BoxSizer(wx.HORIZONTAL)
panelSizer = wx.BoxSizer(wx.HORIZONTAL)
self.sizerA = sizerA = wx.BoxSizer(wx.VERTICAL)
self.panelA = panelA = wx.lib.scrolledpanel.ScrolledPanel(self, -1, style=wx.SIMPLE_BORDER)
panelA.SetupScrolling()
panelA.SetBackgroundColour('#FFFFFF')
self.sizerB = sizerB = wx.BoxSizer(wx.VERTICAL)
self.panelB = panelB = wx.lib.scrolledpanel.ScrolledPanel(self, -1, style=wx.SIMPLE_BORDER)
panelB.SetupScrolling()
panelB.SetBackgroundColour('#FFF000')
panelA.SetSizer(sizerA)
panelB.SetSizer(sizerB)
mainSizer.Add(panelA, 15, wx.EXPAND|wx.ALL)
mainSizer.Add(panelB, 15, wx.EXPAND|wx.ALL)
self.SetSizer(mainSizer)
self.createPanelsA()
self.createPanelsB()
def createPanelsA(self):
k = 0
labelObj = labelsA()
locations = labelObj.getLabel()
print locations
for i in locations:
sPanels = 'sPanel'+str(k)
sPanels = wx.Panel(self.panelA)
label = str(k+1)
text = wx.StaticText(sPanels, -1, label)
text.SetFont(self.locationFont)
text.SetForegroundColour('#0101DF')
self.sizerA.Add(sPanels, 0, wx.ALL, 5)
self.sizerA.Add(wx.StaticLine(self.panelA), 0, wx.ALL|wx.EXPAND, 0)
k += 1
def createPanelsB(self):
k = 0
labelObj = labelsB()
locations = labelObj.getLabel()
print locations
for i in locations:
sPanels = 'sPanel'+str(k)
sPanels = wx.Panel(self.panelB)
label = str(k+1)
text = wx.StaticText(sPanels, -1, label)
text.SetFont(self.locationFont)
text.SetForegroundColour('#0101DF')
self.sizerB.Add(sPanels, 0, wx.ALL, 5)
self.sizerB.Add(wx.StaticLine(self.panelB), 0, wx.ALL|wx.EXPAND, 0)
k += 1
################################################
class labelsA():
def getLabel(self):
mylist =[]
i = randrange(10)
for k in range(1,i+1):
mylist.append(k)
return mylist
###############################################
class labelsB():
def getLabel(self):
mylist =[]
i = randrange(10)
for k in range(1,i+1):
mylist.append(k)
return mylist
###############################################
if __name__=='__main__':
app = wx.App()
frame = GUI(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()
Thank you very much for your time. Any help will be appreciated.
Here is a demonstration of creating a thread to get info from somewhere every 5 seconds, and also showing how to create StaticTexts as you need them, and how to update them.
#!/usr/bin/env python
from random import randrange
import wx
import wx.lib.scrolledpanel
import time
import threading
from wx.lib.pubsub import setupkwargs
from wx.lib.pubsub import pub
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 800
screenHeight = 450
screenSize = (screenWidth, screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.locationFont = locationFont = wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = sizer = wx.BoxSizer(wx.VERTICAL)
self.panel = panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1, style=wx.SIMPLE_BORDER)
panel.SetupScrolling()
panel.SetBackgroundColour('#FFFFFF')
panel.SetSizer(sizer)
mainSizer.Add(panel, 15, wx.EXPAND|wx.ALL)
self.SetSizer(mainSizer)
self.text_labels = [] # Stores the labels where server data is displayed
pub.subscribe(self.OnNewLabels, "NEW_LABELS")
def OnNewLabels(self, labels):
locations = labels
print locations
if len(self.text_labels) < len(labels):
new_labels_needed = len(labels) - len(self.text_labels)
label = "(no data)"
for i in range(new_labels_needed):
sPanels = wx.Panel(self.panel)
text = wx.StaticText(sPanels, -1, label)
text.SetFont(self.locationFont)
text.SetForegroundColour('#0101DF')
self.sizer.Add(sPanels, 0, wx.ALL, 5)
self.sizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 0)
self.text_labels.append(text)
self.sizer.Layout()
k = 0
for label in locations:
self.text_labels[k].SetLabel(str(label))
k=k+1
###############################
#
#
def InterfaceThread():
while True:
# get the info from the server
mylist =[]
i = randrange(10)
for k in range(1,i+1):
mylist.append(randrange(10))
# Tell the GUI about them
wx.CallAfter(pub.sendMessage, "NEW_LABELS", labels = mylist)
time.sleep(5)
class ServerInterface():
def __init__(self):
interface_thread = threading.Thread(target = InterfaceThread, args = ())
interface_thread.start()
#############
#
if __name__=='__main__':
app = wx.App()
frame = GUI(parent=None, id=-1, title="Test")
frame.Show()
server_interface = ServerInterface()
app.MainLoop()
For the record, I know this question was answered a long time ago, the same result can be achieved with a simple wx.Timer.
Using the code supplied above:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import wx.lib.scrolledpanel
from random import randrange
class GUI(wx.Frame):
def __init__(self, parent, id, title):
screenWidth = 800
screenHeight = 450
screenSize = (screenWidth, screenHeight)
wx.Frame.__init__(self, None, id, title, size=screenSize)
self.locationFont = locationFont = wx.Font(15, wx.MODERN, wx.NORMAL, wx.BOLD)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.sizer = sizer = wx.BoxSizer(wx.VERTICAL)
self.panel = panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1, style=wx.SIMPLE_BORDER)
panel.SetupScrolling()
panel.SetBackgroundColour('#FFFFFF')
panel.SetSizer(sizer)
mainSizer.Add(panel, 15, wx.EXPAND|wx.ALL)
self.SetSizer(mainSizer)
self.text_labels = [] # Stores the labels where server data is displayed
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(5000)
def OnNewLabels(self, labels):
locations = labels
print locations
if len(self.text_labels) < len(labels):
new_labels_needed = len(labels) - len(self.text_labels)
label = "(no data)"
for i in range(new_labels_needed):
sPanels = wx.Panel(self.panel)
text = wx.StaticText(sPanels, -1, label)
text.SetFont(self.locationFont)
text.SetForegroundColour('#0101DF')
self.sizer.Add(sPanels, 0, wx.ALL, 5)
self.sizer.Add(wx.StaticLine(self.panel), 0, wx.ALL|wx.EXPAND, 0)
self.text_labels.append(text)
self.sizer.Layout()
k = 0
for label in locations:
self.text_labels[k].SetLabel(str(label))
k=k+1
if len(self.text_labels) > len(labels):
labels_not_needed = len(self.text_labels) - len(labels)
for i in range(labels_not_needed):
self.text_labels[k].SetLabel("-")
k+=1
def OnTimer(self, evt):
# get the info from the server
mylist =[]
i = randrange(10)
for k in range(1,i+1):
mylist.append(randrange(10))
self.OnNewLabels(mylist)
if __name__=='__main__':
app = wx.App()
frame = GUI(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()
I am making a frame with a scrollbar and some images inside. The scrollbar works fine when the frame is empty. However, when I add a picture in, the scrollbars seem to get pushed up into the top left corner of the frame. How can I implement my code so that the scrollbars stay where they are after I add pictures?
Working Code;
import wx
import wx.animate
class ScrollbarFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Scrollbar Example', pos = (100, 50), size=(1000, 1000))
self.scroll = wx.ScrolledWindow(self, -1)
self.scroll.SetScrollbars(1, 1, 1000, 1000)
#self.button = wx.Button(self.scroll, -1, "Scroll Me", pos=(50, 20))
#self.Bind(wx.EVT_BUTTON, self.OnClickTop, self.button)
#self.button2 = wx.Button(self.scroll, -1, "Scroll Back", pos=(500, 350))
#self.Bind(wx.EVT_BUTTON, self.OnClickBottom, self.button2)
self.SetBackgroundColour("gray")
imageName = "01 background.png"
gifName = "Jill.gif"
backgroundImage = wx.Image(imageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, backgroundImage,(10,5),(backgroundImage.GetWidth(), backgroundImage.GetHeight()))
gifImage = wx.animate.GIFAnimationCtrl(self, 0, gifName, pos=(160, 74))
# clears the background
gifImage.GetPlayer().UseBackgroundColour(True)
gifImage.Play()
def update(self, imageName, gifName):
backgroundImage = wx.Image(imageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, backgroundImage,(10,5),(backgroundImage.GetWidth(), backgroundImage.GetHeight()))
gifImage = wx.animate.GIFAnimationCtrl(self, 0, gifName, pos=(100, 100))
# clears the background
gifImage.GetPlayer().UseBackgroundColour(True)
gifImage.Play()
def OnClickTop(self, event):
self.scroll.Scroll(600, 400)
def OnClickBottom(self, event):
self.scroll.Scroll(1, 1)
app = wx.PySimpleApp()
frame = ScrollbarFrame()
frame.Show()
app.MainLoop()
if you comment out this part:
gifName = "Jill.gif"
backgroundImage = wx.Image(imageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, backgroundImage,(10,5),(backgroundImage.GetWidth(), backgroundImage.GetHeight()))
gifImage = wx.animate.GIFAnimationCtrl(self, 0, gifName, pos=(160, 74))
# clears the background
gifImage.GetPlayer().UseBackgroundColour(True)
gifImage.Play()
the window displays properly with the scrollbar. But include either (or both) of the image files, and the problem occurs.
If you want your images inside the scrolled window panel, then you have to put your static bipmap and gifImage inside it. So the parent of your images should not be self (the wx.Frame instance) but self.scroll.
Modify the 4 lines indicated:
...................
wx.StaticBitmap(self.scroll, -1, backgroundImage,(10,5),(backgroundImage.GetWidth(), backgroundImage.GetHeight())) # <- this one
gifImage = wx.animate.GIFAnimationCtrl(self.scroll, 0, gifName, pos=(160, 74)) # <- this one
# clears the background
gifImage.GetPlayer().UseBackgroundColour(True)
gifImage.Play()
def update(self, imageName, gifName):
backgroundImage = wx.Image(imageName, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self.scroll, -1, backgroundImage,(10,5),(backgroundImage.GetWidth(), backgroundImage.GetHeight())) # <- this one
gifImage = wx.animate.GIFAnimationCtrl(self.scroll, 0, gifName, pos=(100, 100)) # <- this one
...................
This puts your two images one over the other. If you want to put them separately (column or row), then you should add them to a sizer inserted in your scrolled window