wxPython: How to make a TextCtrl fill a Panel - python

How do I set the size of a multi-line TextCtrl to always fill its parent panel?

Use a boxSizer.
When you add your textCtrl to the sizer set the proportion to 1 and pass the wx.EXPAND flag, that way your textCtrl should fill the panel even when the panel is resized
bsizer = wx.BoxSizer()
bsizer.Add(yourTxtCtrl, 1, wx.EXPAND)
Put the following at the end of your panels initialization to set the layout
self.SetSizerAndFit(bsizer)

Related

wxPython how to move items to new row if they become hidden?

I have a collection of buttons arranged in a row.
Here is a simplified example of my code:
import wx
app = wx.App(False)
frame = wx.Frame(None)
panel = wx.Panel(frame, wx.ID_ANY)
box = wx.BoxSizer(wx.HORIZONTAL)
for i in range(10):
btn = wx.Button(panel, wx.ID_ANY, "Test")
box.Add(btn, 1, wx.FIXED_MINSIZE)
panel.SetSizer(box)
frame.Show()
app.MainLoop()
Making the window smaller leads to the buttons being hidden, as would be expected. However, I want the buttons to retain their dimensions and be moved to a second row instead of shrinking and finally disappearing. I have tried using different settings and different types of sizers but with no avail. How can I go about doing this?
Any help would be greatly appreciated.
I just found out that wxPython has a sizer that does this. For some reason I didn't notice it on the docs before. For anyone stumbling on this in the future, you'll want to use wx.WrapSizer instead of wx.BoxSizer.

wxPython - change the formatting and appearance of a grid object

I would like to know if it is possible to change the formatting of a grid object in wxPython.
Specifically I would like to know how to change the background color, font type, font color, and border color of the header labels and the row labels.
Here is a simple grid that I would like to change the formatting of:
import wx
import wx.grid as gridlib
class MyForm(wx.Frame):
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, parent=None, title="A Simple Grid")
panel = wx.Panel(self)
myGrid = gridlib.Grid(panel)
myGrid.CreateGrid(12, 8)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(myGrid, 1, wx.EXPAND)
panel.SetSizer(sizer)
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
Thanks in advance!
First off, wx.PySimpleApp has been deprecated for a very long time and you really shouldn't use it any more. Use wx.App instead.
As for your question you would need to use wx.lib.mixins.gridlabelrenderer. The demo has an example in it called GridLabelRenderer that you can use. Basically you subclass GridLabelRenderer and modify its Draw method. Then you call each column's SetColLabelRenderer method and set it to use your custom renderer.

Resizing wxPython Widgets

How do I change the size of wxPython widget after its creation? I am trying to dynamically change the width of a ComboBoxCtrl widget based on the elements in the popup. For the sake of simplicity lets say we create a button and try to resize it:
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetSize(wx.Size(25,25))
self.Layout()
In the above case, the new 25x25 size does not take. I can only get SetSize to work for a panel. I am assuming there is another call I should be using. How can I change the size of widget after creation?
The SetMinSize command does the trick.
btn = wx.Button(self, wx.ID_ANY, 'Start', size=(25,-1))
btn.SetMinSize(wx.Size(25,25))
self.Layout()

wxpython creataStatusBar strange behavior

I have some wxpython code that behave strangely.. this is OKAY code:
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title,size=(500, 300))
self.CreateStatusBar()
panel = wx.Panel(self)
self.srcSizer = wx.BoxSizer(wx.HORIZONTAL)
srcButton = wx.Button(panel, wx.ID_ANY, "src")
srcButton.Bind(wx.EVT_BUTTON, self.onSrcButton)
self.srcSizer.Add(srcButton, 0)
self.srcTxt = wx.TextCtrl(panel, wx.ID_ANY)
self.srcSizer.Add(self.srcTxt, 1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.srcSizer, 0 , flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=10)
panel.SetSizer(self.sizer)
self.Show(True)
now when I swap the two lines of creating statusbar and the panel so they become
panel = wx.Panel(self)
self.CreateStatusBar()
Then the button and the textctrl are overlapped when the window is loaded, and they are brought back to normal position when resizing the window manually !!
Does self.CreatStatusBar() have always to be before creating panels or something ?
Thanks
CreateStatusBar() triggers a resize event on the frame to make room for the status bar. If the panel was already created, it is resized to fit the client
area of the frame. Resizing the panel triggers a resize event on the panel which will then recalulate its layout (the sizer) if applicable.
Creating controls (to be added to the panel's sizer) simply places them at the default position (0,0) with their default size. They will require
a layout (sizer) update to be moved to their correct position. (that's why there's a pile of controls at the upper left corner when the problem occurs.)
When the frame is shown, a resize event is fired (again). However, if the panel already fits the client area of the frame, the panel's resize event is not triggered,
therefore its layout is not updated.
You can observe that effect by creating the panel with the size of the client area even without the status bar:
panel = wx.Panel(self,size=self.GetClientSize())
#self.CreateStatusBar()
Likewise, you can trigger the update by setting the size of the panel to something else (once the frame is shown, it will resize the panel again):
# at the end of __init__
panel.SetSize(0,0)
However, this would create an unnecessary resize: first when you manually SetSize() and again in frame.Show(). There's a better way (shown below).
In wx 2.8, this issue applies to CreateToolBar() as well. wx 2.9.4 appears to handle the toolbar correctly.
As a workaround, you can:
create toolbar/statusbar before you create the panel or after you set the panel's sizer (that requires to add the controls to the sizer first)
recalculate the panel's layout after you set the sizer:
panel.SetSizer(sizer)
panel.Layout()

wxPython minimal size of Frame with a Panel

wxpython 2.8.11.0, python 2.7
If i put some Sizer with some controls directly into a Frame like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(frm))
sizer.Add(wx.SpinCtrl(frm))
frm.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
the Frame will automagically have a correct minimum size to contain the Sizer and it is not possible to make it smaller.
If there is a Panel in between (as needed for tabbing between controls) this does not work, the window can be made too small.
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Show()
app.MainLoop()
Additionally frm.Fit() and frm.SetMinSize(frm.GetEffectiveMinSize()) are required to get the same behavior. Full Code:
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
frm.Show()
app.MainLoop()
I am okay with frm.Fit(), but i dislike frm.SetMinSize(frm.GetEffectiveMinSize()). Isn't there a better solution so that the Frame automatically considers the minimum size of the Panel just like with the Sizer before? I'm considered with what happens if the EffectiveMinSize changes after another control is added to the sizer.
Edit:
Apparently
panel.SetSizerAndFit(sizer)
frm.Fit()
frm.SetMinSize(frm.GetEffectiveMinSize())
should be replaced by
panel.SetSizer(sizer)
sizer.SetSizeHints(frm)
which looks somewhat cleaner. So in total this looks like
import wx
app=wx.App()
frm = wx.Frame(None, title='title')
pan = wx.Panel(frm)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(wx.SpinCtrl(pan))
sizer.Add(wx.SpinCtrl(pan))
pan.SetSizer(sizer)
sizer.SetSizeHints(frm)
frm.Show()
app.MainLoop()
Is this the preferred way to do this?
If widgets are added later on even the first approach with the Sizer directly on the Frame doesn't get it right without intervention, so i think that is a totally different question.
The most elegant way (IMHO) is given in phineas' answer. It is slightly inefficient to have an extra otherwise unneeded sizer but I don't think it can really be noticeable.
Some people do call SetSizeHints() manually, as in your last example, and this works as well and might be more clear (whenever I use an extra sizer I feel the need to leave a comment explaining why it shouldn't be removed).
Unfortunately there is no better way and I am not sure if we can even add one because you need to do something with all of the panel, the sizer and the frame and this means that you can't do it with a single method call without passing unrelated parameters to it. I.e. we could have something like
panel.SetSizerAndFitParent(sizer, frame);
but it's not really clear whether this would be better.
What about introducing a sizer that manages the frame content (e.g. the panel)? Since you didn't include a minimal example, I haven't tested it yet.
frameSizer = wx.BoxSizer(wx.VERTICAL)
frameSizer.Add(panel)
frm.SetSizerAndFit(frameSizer)

Categories

Resources