wxPython ScrolledWindow not working when used in a panel - python

I am having trouble nesting a ScrolledWindow inside a wx.Panel.
If I create a scrolled window on its own it seems to work, however when I create it inside a wx.Panel and add the wx.Panel to the frames sizer it does not. Is there anything that I am missing?
Note:
#pa = AScrolledWindow(self) <-- if uncommented this works
pa = ScrolledWindowHolder(self) # However this does not!
import wx
class ScrolledWindowHolder(wx.Panel):
def __init__(self, parent):
super(ScrolledWindowHolder, self).__init__(parent=parent)
mysizer = wx.GridBagSizer()
self.myscrolledWindow = AScrolledWindow(self)
mysizer.Add(self.myscrolledWindow, pos=(0, 0), flag=wx.EXPAND)
self.SetSizerAndFit(mysizer)
class AScrolledWindow(wx.ScrolledWindow):
def __init__(self, parent):
super(AScrolledWindow, self).__init__(parent)
gb = wx.GridBagSizer()
self.sizer = gb
self._labels = []
for y in xrange(1, 30):
self._labels.append(wx.StaticText(self, -1, "Label #%d" % (y,)))
gb.Add(self._labels[-1], (y, 1), (1, 1))
self.SetSizer(self.sizer)
self.SetScrollRate(5, 5)
self.EnableScrolling(True, True)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Programmatic size change')
sz = wx.BoxSizer(wx.VERTICAL)
#pa = AScrolledWindow(self)
pa = ScrolledWindowHolder(self)
sz.Add(pa, 1, wx.EXPAND)
self.SetSizer(sz)
def main():
wxapp = wx.App()
fr = TestFrame()
fr.Show(True)
wxapp.MainLoop()
if __name__ == '__main__':
main()

Not sure why but the issue appears to be with the fact that you are using a GridBagSizer with a single widget in ScrolledWindowHolder, which itself contains a GridBagSizer.
If you change ScrolledWindowHolder to use a BoxSizer it works, as expected.
class ScrolledWindowHolder(wx.Panel):
def __init__(self, parent):
super(ScrolledWindowHolder, self).__init__(parent=parent)
mysizer = wx.BoxSizer(wx.HORIZONTAL)
self.myscrolledWindow = AScrolledWindow(self)
mysizer.Add(self.myscrolledWindow, 1, wx.EXPAND,0)
self.SetSizerAndFit(mysizer)
Also, change the value of y to for y in range(1, 60): will demonstrate the scrolled window more effectively.

Related

wxPython: How to make sizer fill its parent entirely

I have a problem where BoxSizer doesn't fill its parent.
In the above screenshot I mean the sizer containing yellow and purple panels. I want this sizer and the panels to fill the entire panel in Main tab.
The only way I found to accomplish this is to SetMinSize() on the sizer to some big value. I can't set it to panel's actual size because GetSize() on the panel returns very small and definitely not real values.
Here's the relevant code:
import wx
class App(wx.Frame):
"""Main app window wrapping around everything else.
"""
def __init__(self):
super(App, self).__init__(None, title='TSP Visual', size=(1200, 900))
self.init_ui()
self.Centre()
self.Show()
def init_ui(self):
# Menubar
menu_bar = wx.MenuBar()
file_menu = wx.Menu()
exit_mi = file_menu.Append(wx.ID_EXIT, 'Exit', 'Exit application')
menu_bar.Append(file_menu, 'File')
self.SetMenuBar(menu_bar)
# Main layout
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
# Title
title = wx.StaticText(panel, label='No instance loaded')
title_font = wx.Font(wx.FontInfo(18))
title.SetFont(title_font)
title.SetMinSize(title.GetTextExtent(title.Label))
sizer.Add(title, 0, wx.EXPAND | wx.ALL, 10)
# Tabs
notebook = wx.Notebook(panel)
main_tab = MainTab(notebook)
stats_tab = StatsTab(notebook)
notebook.AddPage(main_tab, 'Main')
notebook.AddPage(stats_tab, 'Stats')
sizer.Add(notebook, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
panel.SetSizerAndFit(sizer)
# Event bindings
self.Bind(wx.EVT_MENU, lambda e: self.Close(), exit_mi)
class MainTab(wx.Panel):
"""Main tab of the app, solver controls and tsp view.
"""
def __init__(self, parent):
super(MainTab, self).__init__(parent)
self.init_ui()
def init_ui(self):
# Panel sizer
sizer = wx.BoxSizer(wx.HORIZONTAL)
# Solver controls and TSP view
controls = SolverControls(self)
controls.SetBackgroundColour('yellow')
tsp_view = TSPView(self)
tsp_view.SetBackgroundColour('purple')
sizer.Add(controls, 1, wx.EXPAND)
sizer.Add(tsp_view, 1, wx.EXPAND)
self.SetSizerAndFit(sizer)
class StatsTab(wx.Panel):
"""Second tab, graphs and statistics
"""
def __init__(self, parent):
super(StatsTab, self).__init__(parent)
self.init_ui()
def init_ui(self):
pass
class TSPView(wx.Panel):
def __init__(self, parent):
super(TSPView, self).__init__(parent)
self.init_ui()
def init_ui(self):
self.SetBackgroundColour('white')
class SolverControls(wx.Panel):
def __init__(self, parent):
super(SolverControls, self).__init__(parent)
self.init_ui()
def init_ui(self):
sizer = wx.GridBagSizer()
text = wx.StaticText(self, label='Test text')
sizer.Add(text, (0, 0), (1, 1), wx.ALL, 5)
button1 = wx.Button(self, label='Button 1')
sizer.Add(button1, (1, 0), (1, 1), wx.ALL, 5)
button2 = wx.Button(self, label='Button 2')
sizer.Add(button2, (2, 0), (1, 1), wx.ALL, 5)
self.SetSizer(sizer)
if __name__ == '__main__':
app = wx.App()
App()
app.MainLoop()
EDIT:
I've changed my code sample so it's self contained and runnable.
You shouldn't call SetSizerAndFit() in MainTab.init_ui: by calling this function, you change the size of MainTab to be just big enough to fit its contents. The yellow and purple panels should still get resized properly once you resize the parent window (if they don't, it means that there is another problem somewhere else, which I've missed), but to make it work from the beginning, just use SetSizer() instead.
So it turned out the problem was somehow related to i3wm window manager. Since manually resizing the window fixes the problem I came up with a solution where I SetSize() of the window after Show()ing it. My __init__() method of App looks like this:
def __init__(self):
super(App, self).__init__(None, title='TSP Visual')
self.init_ui()
self.Show()
self.SetSize(1200, 900)
self.Centre()

wxPython : Update the label on Panel with GridBagSizer and Timer

I want to update the panel "label", but I think I am wrong with Refresh/Update/Remove method .
I write 2 python file, the "WriteData.py" would auto-update a txt file, and the "Main.py" want to show the txt value on wx.panel.
I run the 2 python file at the same time, use Timer to auto update data every 3 sec .
And I use the GridBagSizer hope to arrange these panel position.
But I don't know how to arrange the new updating panel position, Also don't know how to remove previous panel .
Hope you give me some advice, or even point out my mistake.
I also appreciate for some example code about this !
Here is the "Main.py"
import wx
import time
def ReadData():
with open('RealTime.txt') as f:
for line in f:
data = line.split()
results = map(float, data)
return results
class BlockWindow(wx.Panel):
# code on book "wxPython in action" Listing 11.1
def __init__(self, parent, ID=-1, label="",
pos = wx.DefaultPosition, size = (100, 25)):
wx.Panel.__init__(self, parent, ID, pos, size,
wx.RAISED_BORDER, label)
self.label = label
self.SetMinSize(size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
sz = self.GetClientSize()
dc = wx.PaintDC(self)
w,h = dc.GetTextExtent(self.label)
dc.SetFont(self.GetFont())
dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, size=(0,0))
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(3000)
def OnTimer(self, evt):
Data = ReadData()
sizer = wx.GridBagSizer(hgap=5, vgap=-1)
bw = BlockWindow(self, label="Item 1" )
sizer.Add(bw, pos=(4, 2))
#bw.Refresh()
bw = BlockWindow(self, label="Updated : %.3f" % Data[0])
sizer.Add(bw, pos=(5, 2))
bw.Refresh()
#bw.Update(self, label ="Updated : %.3f" % Data[0] )
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(sizer, 0, wx.EXPAND|wx.ALL, 10)
self.SetSizer(mainSizer)
self.Fit()
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title=' Frame Title')
mypanel = MyPanel(self)
self.SetSize(wx.Size(800,600))
self.Centre()
app = wx.App(False)
MyFrame().Show()
app.MainLoop()
Here is the 'WriteData.py',
import sched, time
from datetime import datetime as dt
data = ['5.564', '3.4', '2.176', '7.3', '4.4', '5.5', '2.3', '4.4', '5.1']
index = 0
while True:
start = dt.now().hour
stop = dt.now().hour + 1
if index >7 : index=1
if dt.now().hour in range(start, stop): # start, stop are integers (eg: 6, 9)
# call to your scheduled task goes here
f2 = open('RealTime.txt', 'w')
f2.write("%s " % data[index])
index = index + 1
f2.close()
time.sleep(3)
else:
time.sleep(3)
When I run the 2 .py file , I got this situation Running example
Hope you help me solve this .
I use python2.7 on win10.
Best regards, Kuo-Ting Tang
You don't need to recreate everything from scratch each time an update is needed. Just move the initialization code (where you create BlockWindows and sizers to the constructor of MyPanel. It seems that all you want to do is update the label of the second panel, to achieve this you could write a method in BlockWindow that will update the label and call Refresh so that OnPaint will be triggered and will take care of the rest.
class BlockWindow(wx.Panel):
# code on book "wxPython in action" Listing 11.1
def __init__(self, parent, ID=-1, label="",
pos = wx.DefaultPosition, size = (100, 25)):
wx.Panel.__init__(self, parent, ID, pos, size,
wx.RAISED_BORDER, label)
self.label = label
self.SetMinSize(size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
sz = self.GetClientSize()
dc = wx.PaintDC(self)
w,h = dc.GetTextExtent(self.label)
dc.SetFont(self.GetFont())
dc.DrawText(self.label, (sz.width-w)/2, (sz.height-h)/2)
def UpdateLabel(self, label):
self.label = label
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, size=(0,0))
sizer = wx.GridBagSizer(hgap=5, vgap=-1)
bw = BlockWindow(self, label="Item 1" )
sizer.Add(bw, pos=(4, 2))
self.block = BlockWindow(self, label="")
sizer.Add(self.block, pos=(5, 2))
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(sizer, 0, wx.EXPAND|wx.ALL, 10)
self.SetSizer(mainSizer)
self.Fit()
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(3000)
def OnTimer(self, evt):
Data = ReadData()
self.block.UpdateLabel("Updated : %.3f" % Data[0])

Plot figure problems with python and matplotlib

I'm making an app that plots a figure after some processing. This is done after the user has introduced some values and pushes a button. However I don't get the figure plotted. Below there is a simplified code. This works fine if I plot directly the values of t and s, but not if it is done after pushing the button. What am I missing? Is there another better way to do so?
from numpy import arange, sin, pi
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
import wx
class Input_Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# Input variables
self.button = wx.Button(self, label="Go")
# Set sizer for the panel content
self.sizer = wx.GridBagSizer(1, 1)
self.sizer.Add(self.button, (1, 2), (3, 6), flag=wx.EXPAND)
self.SetSizer(self.sizer)
class Output_Panel_Var(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# Output variables
self.tittle = wx.StaticText(self, label="OUTPUTS:")
self.font = wx.Font(12, wx.DECORATIVE, wx.BOLD, wx.NORMAL)
self.tittle.SetFont(self.font)
self.lblt = wx.StaticText(self, label="t:")
self.resultt = wx.StaticText(self, label="", size=(100, -1))
self.lbls = wx.StaticText(self, label="s:")
self.results = wx.StaticText(self, label="", size=(100, -1))
# Set sizer for the panel content
self.sizer = wx.GridBagSizer(2, 2)
self.sizer.Add(self.tittle, (1, 3))
self.sizer.Add(self.lblt, (3, 1))
self.sizer.Add(self.resultt, (3, 2))
self.sizer.Add(self.lbls, (4, 1))
self.sizer.Add(self.results, (4, 2))
self.SetSizer(self.sizer)
class Output_Panel_Fig(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
def draw(self,t,s):
self.axes.plot(t, s)
class Main_Window(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title = title, pos = (0, 0), size = wx.DisplaySize())
# Set variable panels
self.main_splitter = wx.SplitterWindow(self)
self.out_splitter = wx.SplitterWindow(self.main_splitter)
self.inputpanel = Input_Panel(self.main_splitter)
self.inputpanel.SetBackgroundColour('#c4c4ff')
self.outputpanelvar = Output_Panel_Var(self.out_splitter)
self.outputpanelvar.SetBackgroundColour('#c2f1f5')
self.outputpanelfig = Output_Panel_Fig(self.out_splitter)
self.main_splitter.SplitVertically(self.inputpanel, self.out_splitter)
self.out_splitter.SplitHorizontally(self.outputpanelvar, self.outputpanelfig)
# Set event handlers
self.inputpanel.button.Bind(wx.EVT_BUTTON, self.OnButton)
def OnButton(self, e):
t = arange(0.0, 1.0, 0.01)
s = sin(2 * pi * t)
#self.outputpanelvar.resultt.SetLabel('%.5f' % t)
#self.outputpanelvar.resultt.SetLabel('%.5f' % s)
self.outputpanelfig.draw(t,s)
def main():
app = wx.App(False)
frame = Main_Window(None, "T-Matrix Codes GUI")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()
I think you are missing a redraw of the canvas. It is not enough to do a new plot but a refresh of the drawing pane must be done! Add a self.canvas.draw() after your plot command in the draw method of the Output_Panel_Fig this should help.
import ...
class Input_Panel(wx.Panel):
def __init__(self, parent):
...
class Output_Panel_Var(wx.Panel):
def __init__(self, parent):
...
class Output_Panel_Fig(wx.Panel):
def __init__(self, parent):
...
def draw(self,t,s):
self.axes.plot(t, s)
self.canvas.draw()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
...
def OnButton(self, e):
...
def main():
...
if __name__ == "__main__" :
main()

Placing two wx Grids side by side

I am using python 2.7 and the latest version of wxpython.
I need to place 2 grids side by side and to be able to add rows dynamically. Ideally, the sizes of grids should grow dynamically, but "big enough" size should be ok for the time being.
How should I change (and simplify) my code to make this happen?
class MatricesFrame(wx.BoxSizer):
def __init__(self, parentPanel):
super(MatricesFrame, self).__init__(wx.HORIZONTAL)
self.outputsMatrix = self.addEmptyGrid(parentPanel)
self.inputsMatrix = self.addEmptyGrid(parentPanel)
addRowsButton = wx.Button(parentPanel, -1, " Add Rows")
addRowsButton.Bind(wx.EVT_BUTTON, self.addRows)
self.Add(addRowsButton)
def initResize(self, ev = None):
self.inputsMatrix.SetSize((500, 500))
self.outputsMatrix.SetSize((500, 500))
def addEmptyGrid(self, parentPanel):
panel_ = wx.Panel(parentPanel)
sizer_ = wx.BoxSizer(wx.VERTICAL)
panel_.SetSizer(sizer_)
panel_.SetSize((500, 500))
matrix_ = wx.grid.Grid(panel_)
matrix_.SetRowLabelSize(0)
matrix_.SetColLabelSize(0)
matrix_.CreateGrid(1,1)
sizer_.Add(matrix_)
self.Add(panel_)
return matrix_
def addRows(self, ev=None):
self.inputsMatrix.AppendRows(1)
self.outputsMatrix.AppendRows(1)
class TestFrame(wx.Frame):
def __init__(self, parent):
super(TestFrame, self).__init__(parent, title='test', size=(1280, 950))
panel = wx.Panel(self)
box = wx.BoxSizer(wx.VERTICAL)
self.matricesFrame = MatricesFrame(panel)
box.Add(self.matricesFrame)
panel.SetSizer(box)
self.matricesFrame.initResize()
self.Centre()
self.Show()
self.matricesFrame.initResize()
wx.EVT_IDLE(wx.GetApp(), wx.WakeUpIdle())
def main():
app = wx.App(False)
t= TestFrame(None)
app.MainLoop()
if __name__ == '__main__':
main()
For starters, if you convert your custom Sizer into a custom Panel I think it makes your code a lot easier to control. Take a look at what I came up with
class MatricesPanel(wx.Panel):
def __init__(self, parent):
super(MatricesPanel, self).__init__(parent)
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.outputsMatrix = self.addEmptyGrid(sizer)
self.inputsMatrix = self.addEmptyGrid(sizer)
addRowsButton = wx.Button(self, -1, " Add Rows")
addRowsButton.Bind(wx.EVT_BUTTON, self.addRows)
sizer.Add(addRowsButton)
self.SetSizer(sizer)
self.SetAutoLayout(True)
self.Layout()
def initResize(self, ev = None):
self.inputsMatrix.SetSize((500, 500))
self.outputsMatrix.SetSize((500, 500))
self.Layout()
def addEmptyGrid(self, sizer):
matrix_ = wx.grid.Grid(self)
matrix_.SetRowLabelSize(0)
matrix_.SetColLabelSize(0)
matrix_.CreateGrid(1,1)
sizer.Add(matrix_)
return matrix_
def addRows(self, ev=None):
self.inputsMatrix.AppendRows(1)
self.outputsMatrix.AppendRows(1)
self.Layout() #refresh the frame
Working with a Panel instead of a Sizer you greatly simplify your "addEmptyGrid" method as well as now you can try setting the size of your matricies using the size of the panel, not the size of the matrices themselves. Also, this allows you the flexibility to change from wx.Panel to wx.lib.scrolledpanel.ScrolledPanel if you wanted to add scroll bars (for if you add a lot of rows).
You can then init your new panel as below:
class TestFrame(wx.Frame):
def __init__(self, parent):
super(TestFrame, self).__init__(parent, title='test', size=(1280, 950))
self.matricesPanel = MatricesPanel(self)
sizer = wx.BoxSizer()
sizer.Add(self.matricesPanel, flag=wx.EXPAND)
self.SetSizer(sizer)
self.Centre()
self.Show()
self.matricesPanel.initResize()
wx.EVT_IDLE(wx.GetApp(), wx.WakeUpIdle())
Finally, so far as I can tell, columns in a Grid have a fixed width, so your matrix.SetSize((500, 500)) calls arent' doing much. If you can find a way to set the width of the column then I suggest you rewrite initResize() to set the width relative to the width of the panel.

Put an AuiManager inside a AuiNotebook page

Is it possible ho put an AuiManager inside an AuiNotebook page?
Have tested with a small sample code, but I only get a 'Segmentation fault'.
Is this possible to begin with? The reason why I want this is to split a notebook page in two parts and get the caption field and the maximize field in the top of each part of the two parts. A simple splitterwindow would work but does not look as good and cannot be maximized as easily. And nor does it have the caption field.
Sample code below.
import wx
import wx.aui
import wx.lib.inspection
class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.mgr = wx.aui.AuiManager(self)
self.left = wx.Panel(self, -1, size = (200, 150))
self.right = wx.aui.AuiNotebook(self, -1, size = (200, 150))
self.bottom = wx.Panel(self, -1, size = (200, 150))
self.mgr.AddPane(self.bottom, wx.aui.AuiPaneInfo().Bottom())
self.mgr.AddPane(self.left, wx.aui.AuiPaneInfo().Left().Layer(1))
self.mgr.AddPane(self.right, wx.aui.AuiPaneInfo().CenterPane())
self.new_panel('Panel 1')
self.mgr.Update()
self.Update()
def new_panel(self, nm):
pnl = wx.Window(self)
pnl.identifierTag = nm
self.right.AddPage(pnl, nm, select = True)
self.sizer = wx.BoxSizer()
self.sizer.Add(self.right, 1, wx.EXPAND)
self.SetSizer(self.sizer)
pnl.SetFocus()
mgr = wx.aui.AuiManager(pnl)
left = wx.Panel(self)
right = wx.Panel(self)
mgr.AddPane(left, wx.aui.AuiPaneInfo().Left())
mgr.AddPane(right, wx.aui.AuiPaneInfo().Right())
mgr.Update()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, '07_wxaui.py')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = MyApp(0)
wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
wxAUIManager only works as a child of a wxFrame.
http://docs.wxwidgets.org/trunk/classwx_aui_manager.html

Categories

Resources