How to dynamically position elements in wxPython - python

I've not been able to find an answer in the documentation. Say I have the following:
import wx
from wx import *
import sys
app = wx.App()
def quitProgram(*args):
sys.exit()
def restart(*args):
app.MainLoop()
xSize = 500
ySize = 300
window = wx.Frame(None, title = "My GUI", size = (xSize,ySize))
panel = wx.Panel(window)
# generic label
labelLeft = wx.StaticText(panel, label = 'some text', pos = (2,30))
# exit button
exit = wx.Button(panel, -1, label="Exit", pos = (1, 1), size=(-1,-1))
exit.Bind(wx.EVT_BUTTON, quitProgram)
# reset button
reset = wx.Button(panel, -1, label="Refresh", pos = (100,1), size=(-1,-1))
reset.Bind(wx.EVT_BUTTON, restart)
window.Show(True)
app.MainLoop()
How can I position the objects- buttons and labels- based on the size of the main window? I'd like the objects to reposition based on resizing the window.

The code posted is not clean wxPython code.
You must instantiate your Frame as a Class and use Sizers to automate positioning of your widgets.
A minimal code that reproduces your frame using Sizers is this:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((500, 300))
self.bt_exit = wx.Button(self, wx.ID_ANY, "exit")
self.bt_refresh = wx.Button(self, wx.ID_ANY, "refresh")
self.text_ctrl = wx.TextCtrl(self, wx.ID_ANY, "some text", style=wx.TE_MULTILINE)
self.SetTitle("My GUI")
self.bt_exit.Bind(wx.EVT_BUTTON, self.on_exit)
self.bt_refresh.Bind(wx.EVT_BUTTON, self.on_refresh)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.bt_exit, 1, 0, 0)
sizer_2.Add(self.bt_refresh, 1, 0, 0)
sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
sizer_1.Add(self.text_ctrl, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
def on_exit(self, evt):
self.Close()
def on_refresh(self, evt):
self.text_ctrl.Clear()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
This is well explained in wxPython/Phoenyx docs. Check for example these tutorials

Related

How to create square LED shape by using StaticBox in wxPython

I am trying to create LED shape in my GUI.
My example code is this.
import wx
class Main(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, title ="Static box test")
panel = wx.Panel(self)
self.myLED = wx.StaticBox(panel, -1, "myLED", pos = (50,50), size = (100,100))
self.myLED.SetBackgroundColour("blue")
self.myLED.SetForegroundColour("white")
if __name__ == "__main__":
app = wx.App()
frame = Main()
frame.Show()
app.MainLoop()
The GUI looks like this.
I wanted to create a square LED having a label in the center of the square.
But the LED doesn't look like a perfect square, and the label is on upper-left side.
How could I fix it?
Here is an example using a wx.Panel for the LED and a wx.StaticText inside for the text. The answer lies in the use of the Sizers.
Doc: https://wxpython.org/Phoenix/docs/html/sizers_overview.html
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import wx
class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((400, 300))
# The led panel
self.ledPanel = wx.Panel(self, wx.ID_ANY)
self.__set_properties()
self.__do_layout()
def __set_properties(self):
# begin: MainFrame.__set_properties
self.SetTitle("frame")
self.ledPanel.SetMinSize((100, 100))
self.ledPanel.SetBackgroundColour(wx.Colour(50, 153, 204))
def __do_layout(self):
# main sizer which contains the ledPanel (wx.Panel)
mainSizer = wx.FlexGridSizer(1, 1, 0, 0)
# the Gridsizer is used to correctly place the label in the center (wx.StaticText)
myGridSizer = wx.GridSizer(1, 1, 0, 0)
myLabel = wx.StaticText(self.ledPanel, wx.ID_ANY, "My LED")
myLabel.SetForegroundColour(wx.Colour(255, 255, 255))
myLabel.SetFont(wx.Font(15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, "Segoe UI"))
#Adds the label to the sizer:
myGridSizer.Add(myLabel, 0, wx.ALIGN_CENTER, 0)
#Sets the sizer to the panel (panel supports background color)
self.ledPanel.SetSizer(myGridSizer)
#Adds the panel to the mainSizer.
mainSizer.Add(self.ledPanel, 1, wx.ALIGN_CENTER, 0)
# Sets the mainSizer as the main frame sizer
self.SetSizer(mainSizer)
mainSizer.AddGrowableRow(0)
mainSizer.AddGrowableCol(0)
self.Layout()
# end of class MainFrame
# Class MyApp (to launch my app)
class MyApp(wx.App):
def OnInit(self):
self.frame = MainFrame(None, wx.ID_ANY, "")
self.SetTopWindow(self.frame)
self.frame.Show()
return True
# end of class MyApp
# Main method:
if __name__ == "__main__":
app = MyApp(0)
app.MainLoop()
And this is what you will get when you run it
Hope this can help you.

wxpython textctrl line and column

I am writing a text editor using wxpython. I want to display the line & column numbers based on the position of the text pointer(when changed using keyboard or mouse), in the statusbar.
I tried to do it using the below code:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_DOWN, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
self.StatusBar.SetStatusText(str(l)+","+str(c), number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()
I tried the below 2 ways to achieve that:
1.) lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
2.) l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
But, both, always give the line & column values of previous position of the pointer instead of current.
Is there a way to display the current line & column values?
I think you just need to bind to EVT_KEY_UP instead of EVT_KEY_DOWN. That way the event handler isn't called until after you have finished typing. I also updated the content written to the statusbar to make it a bit clearer which value was which:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_UP, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
stat = "col=%s, row=%s" % (l,c)
self.StatusBar.SetStatusText(stat, number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()

How to add and edit time in a textctrl in wxpython?

I am developing a GUI using wxpython where i need a textctrl which selects the time .I tried with TimePickerCtrl but failed to fetch the time into the textctrl. It would be great if anyone shares a good example code which adds a time to a textctrl and can be edit the textctrl at any time.Thanks in advance.
Did you even look at the wxPython demo? It shows 3 different ways to create the picker control:
import wx
import wx.lib.masked as masked
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
# 12-hour format
text1 = wx.StaticText( self, -1, "12-hour format:", size=(150,-1))
self.time12 = masked.TimeCtrl( self, -1, name="12 hour control" )
h = self.time12.GetSize().height
spin1 = wx.SpinButton(
self, -1, wx.DefaultPosition, (-1,h), wx.SP_VERTICAL )
self.time12.BindSpinButton( spin1 )
self.addWidgets([text1, self.time12, spin1])
# 24-hour format
text2 = wx.StaticText( self, -1, "24-hour format:")
spin2 = wx.SpinButton(
self, -1, wx.DefaultPosition, (-1,h), wx.SP_VERTICAL )
self.time24 = masked.TimeCtrl(
self, -1, name="24 hour control", fmt24hr=True,
spinButton = spin2
)
self.addWidgets([text2, self.time24, spin2])
# No seconds\nor spin button
text3 = wx.StaticText( self, -1, "No seconds\nor spin button:")
self.spinless_ctrl = masked.TimeCtrl(
self, -1, name="spinless control",
display_seconds = False
)
self.addWidgets([text3, self.spinless_ctrl])
# set sizer
self.SetSizer(self.mainSizer)
def addWidgets(self, widgets):
sizer = wx.BoxSizer(wx.HORIZONTAL)
for widget in widgets:
if isinstance(widget, wx.StaticText):
sizer.Add(widget, 0, wx.ALL|wx.CENTER, 5),
else:
sizer.Add(widget, 0, wx.ALL, 5)
self.mainSizer.Add(sizer)
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Spinner Demo")
panel = MyPanel(self)
self.Show()
if __name__ == "__main__":
app = wx.App(False)
f = MyFrame()
app.MainLoop()

Update/Refresh Dynamically–Created WxPython Widgets

New python programmer here and trying to learn how to dynamically update widgets. To start, I have the following code. What I would like to do is change my variable "self.dynamiclength" to any integer, and have WxPython update the number of widgets appropriately. I have tried putting self.Refresh() and self.Update() in my TestFrame after updating self.dynamiclength to no avail.
I have done as much reading as possible on this before resorting to asking for help, but I am just too new at Wx to solve this one on my own. Thank you much!
import wx
import wx.lib.scrolledpanel as scrolled
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(1000, 550))
panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(panel, label="Back0")
controlforward0 = wx.Button(panel, label="Forward0")
pbox0.Add(controlback0, 0, wx.ALL)
pbox0.Add(controlforward0, 0, wx.ALL)
mainSizer.Add(pbox0)
self.scrolling_window = scrolled.ScrolledPanel( panel )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.child_windows = []
##############################################
#this is the variable that I want to change,
#and I don't know how to get the 'for loop'
#below to update as well.
self.eedictionary = {}
self.dynamiclength = 5
for i in range(0,self.dynamiclength):
wind = self.addBox(i)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
###############################################
#the following code binds all appropriate buttons to a pedigree variable updater
button_binding_list = ['controlback','controlforward']
for j in button_binding_list:
eid = self.eedictionary[str(i)+j]
self.scrolling_window.Bind(wx.EVT_BUTTON, lambda evt: self.onclick(evt, id), id=eid)
self.scrolling_window.SetSizer(self.sizer)
mainSizer.Add(self.scrolling_window, 1, wx.EXPAND)
panel.SetSizer(mainSizer)
def addBox(self, i):
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self.scrolling_window, label="Back")
controlforward = wx.Button(self.scrolling_window, label="Forward")
pbox.AddMany([(controlback, 0, wx.ALL), (controlforward, 0, wx.ALL)])
#for each object created in the addBox module, its id is added to the dictionary
self.eedictionary[str(i)+'controlback'] = controlback.GetId()
self.eedictionary[str(i)+'controlforward'] = controlforward.GetId()
return pbox
def onclick(self, event):
self.dynamiclength +=1
print 'added one to self.dynamiclength', self.dynamiclength
if __name__=='__main__':
app = wx.App(False)
f = TestFrame()
f.Show()
app.MainLoop()
I have similar test code which I have written some time ago. Maybe you will find it useful.
import wx
#===================================================================================================
class UpperPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.combo = wx.ComboBox(self, choices=["0", "1", "2", "3", "4"], size=(200, -1))
self.combo.Bind(wx.EVT_COMBOBOX, self.GetParent().middlePanel.Change)
self.logo = wx.Button(self, size=(300, 100))
self.sizer = wx.BoxSizer()
self.sizer.Add(self.combo, 0, wx.EXPAND)
self.sizer.Add(self.logo, 0, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
#===================================================================================================
class MiddlePanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.subs = []
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizerAndFit(self.sizer)
def Change(self, e):
self.sizer = wx.BoxSizer(wx.VERTICAL)
for a in self.subs:
a.Destroy()
self.subs = []
for a in range(int(e.GetString())):
b = wx.Button(self, size=(-1, 50))
self.subs.append(b)
self.sizer.Add(b, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
self.GetParent().Fit()
#===================================================================================================
class MainWin(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.middlePanel = MiddlePanel(self)
self.upperPanel = UpperPanel(self)
self.textArea = wx.TextCtrl(self, size=(-1, 300), style=wx.TE_MULTILINE)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.upperPanel, 0, wx.EXPAND)
self.sizer.Add(self.middlePanel, 0, wx.EXPAND)
self.sizer.Add(self.textArea, 1, wx.EXPAND)
self.SetSizerAndFit(self.sizer)
#===================================================================================================
if __name__ == '__main__':
app = wx.PySimpleApp()
main_win = MainWin()
main_win.Show()
app.MainLoop()
If you need to update the number of widgets AFTER you've already created and shown the application, the you'll need to do it in a method, NOT in the init. The init only runs the first time the application is instantiated. Whenever you add or remove widgets after the frame is shown, you'll need to call Layout() on the parent widget or its sizer. See also
http://wxpython-users.1045709.n5.nabble.com/dynamically-adding-amp-removing-widgets-td2342432.html
https://groups.google.com/forum/?fromgroups#!topic/wxPython-users/eQjlYlsw4qs
Adding a widget with a button - wxPython

wxPython: wx.Panel of AuiManager has empty space of gray

I am trying to make a window with 2 panels. One panel is just a notebook panel. The second panel contains a toolbar on top and a text control on the bottom. I want to arrange this panel in my frame using wx.aui.AuiManager.
The problem is that I get a big empty space of grey in my custom panel.
Here is my code:
import wx
import wx.aui
import images # contains toolbar icons
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"AUI Tutorial",
size=(600,400))
self._mgr = wx.aui.AuiManager()
self._mgr.SetManagedWindow(self)
notebook = wx.aui.AuiNotebook(self)
nb_panel = TabPanel(notebook)
my_panel = MyPanel(self)
notebook.AddPage(nb_panel, "First Tab", False)
self._mgr.AddPane(notebook,
wx.aui.AuiPaneInfo().Name("notebook-content").
CenterPane().PaneBorder(False))
self._mgr.AddPane(my_panel,
wx.aui.AuiPaneInfo().Name("txtctrl-content").
CenterPane().PaneBorder(False))
self._mgr.GetPane("notebook-content").Show().Top().Layer(0).Row(0).Position(0)
self._mgr.GetPane("txtctrl-content").Show().Bottom().Layer(1).Row(0).Position(0)
self._mgr.Update()
class MyPanel(wx.Panel):
"""
My panel with a toolbar and richtextctrl
"""
def __init__(self,parent):
wx.Panel.__init__(self,parent=parent,id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
toolbar = wx.ToolBar(self,-1)
toolbar.AddLabelTool(wx.ID_EXIT, '', images._rt_smiley.GetBitmap())
self.Bind(wx.EVT_TOOL, self.OnExit, id=wx.ID_EXIT)
toolbar.Realize()
sizer.Add(toolbar,proportion=0,flag=wx.ALL | wx.ALIGN_TOP)
text = ""
txtctrl = wx.TextCtrl(self,-1, text, wx.Point(0, 0), wx.Size(150, 90),
wx.NO_BORDER | wx.TE_MULTILINE | wx.TE_READONLY|wx.HSCROLL)
sizer.Add(txtctrl,proportion=0,flag=wx.EXPAND)
self.SetSizer(sizer)
def OnExit(self,event):
self.Close()
class TabPanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent=parent,id=wx.ID_ANY)
sizer = wx.BoxSizer(wx.VERTICAL)
txtOne = wx.TextCtrl(self, wx.ID_ANY, "")
txtTwo = wx.TextCtrl(self, wx.ID_ANY, "")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txtOne, 0, wx.ALL, 5)
sizer.Add(txtTwo, 0, wx.ALL, 5)
self.SetSizer(sizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show()
app.MainLoop()
So, how do I fix my code so that I don't have that grey block taking up MyPanel? Also, my toolbar button doesn't seem to run self.OnExit(). Why is that?
Thank you for your help.
Take out the line:
self._mgr.GetPane("notebook-content").Show().Top().Layer(0).Row(0).Position(0)
As for the OnExit() handler, it is firing!
If you want to exit the application, replace it with app.Exit()

Categories