I'm taking the first steps to move from .NET to Python but I'm already having a few headaches regarding the GUI design.
For some reason, passing the size attribute to a wx.Button seems to be kind of ignored. And I say "kind of" because the actual space seems to change but the actual button keeps occupying the same space:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
def InitUI(self):
self.SetSize((800, 600))
self.SetTitle('Main Menu')
self.Centre()
self.Show(True)
''' Fill the form '''
self.lblUsername = wx.StaticText(self, size=(80, -1), pos=(20,20), label="Username:" )
self.txtUsername = wx.TextCtrl(self, size=(140, -1), pos=(100,20), style=wx.TE_PROCESS_ENTER)
self.lblPassword = wx.StaticText(self, size=(80, -1), pos=(20,50), label="Password:" )
self.txtPassword = wx.TextCtrl(self, size=(140, -1), pos=(100,50), style=wx.TE_PROCESS_ENTER)
self.btnOK = wx.Button( self, label="OK", pos=(260, 16), size=(50,50))
self.btnOK.Bind(wx.EVT_BUTTON, self.onClickOK)
self.statusbar = self.CreateStatusBar()
self.statusbar.SetStatusText('Ready')
def onClickOK(self, e):
print "Button triggered"
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
No matter what size I set, the Button won't stretch (it will be centered as if all the space was actually being used, but will still be small).
Can anyone spot what am I doing wrong?
This is a limit imposed by OSX. The way the native button widget is drawn only allows it to be stretched horizontally, and the vertical size is fixed. Or rather, as you've discovered, the widget itself can be larger than normal vertically, but it will only draw itself at a fixed height within that space. It seems less neccessary with modern versions of OSX, but if you look at buttons in OSX from a few years ago you can probably see why this is so. The esthetic graphical effect of the "tic-tack" or "capsule" buttons would be totally ruined if they were a non-standard vertical size, causing the images used to draw the buttons to be stretched. wxWidgets follows the native plaform look and feel standards where possible, in this case it happens that Apple's standard is imposed upon us and wx can't offer the same level of flexibility that it usually does.
You do have some options however if you really want taller than normal buttons. The native widgets have a few different standard sizes, which you can select using the SetWindowVariant method, although I don't think the variants would get as tall as you want. Or you could use a generic button widget instead of a native one, such as wx.lib.buttons.ThemedGenButton.
Same problem in my little Software EventSoundControl.
Just a workaround: Use a multiline label and sizes of wxButton will work as desired!
If you want the button to stretch when you resize the frame, then you cannot use static sizes and positioning. You will need to put your widgets in a sizer. Then the sizer will manage the position / size of the widget(s) as you change the size of the frame. There are many examples on the wxPython wiki that demonstrate how to use sizers. You might also find the following tutorial helpful:
http://zetcode.com/wxpython/layout/
Related
So, I'm trying to write a program using wxPython which will have a notebook with tabs that each have the user enter some data into textboxes. This is for a physics related project so I want each textbox to have a "units" label after it displaying what units it should be entered in. The problem is: When the program runs, I get a black rectangle in the upper left corner of each textctrl which is the size of the label that the units are in. The black box disappears forever immediately after any of the following:
press tab onto the textctrl
move the cursor onto the textctrl
switch tabs in the notebook
resize the window until the textctrl has to shrink with it.
functionally everything works fine, I just want to get rid of the black rectangles on startup.
This is my code:
#! usr/bin/python
import wx
class MainWindow(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, size=(400,300))
self.make_gui()
self.Show()
def make_gui(self):
self.panel=wx.Panel(self,wx.ID_ANY)
sizer=wx.BoxSizer(wx.VERTICAL)
ins=self.gui_inputs()
sizer.Add(ins)
self.panel.SetSizer(sizer)
def gui_inputs(self):
sizer=wx.BoxSizer(wx.HORIZONTAL)
simpleinputs=(("Page 1",(("Name",None),("Item","in"))),("sec page",tuple()))
simple=self.gui_inputs_make_simple(simpleinputs)
sizer.Add(simple)
return sizer
def gui_inputs_make_simple(self,simpleinputs):
sizer=wx.BoxSizer(wx.VERTICAL)
notebook=wx.Notebook(self.panel)
for tab in simpleinputs:
pan=wx.Panel(notebook,wx.ID_ANY)
siz=wx.BoxSizer(wx.VERTICAL)
for item in tab[1]:
it=self.gui_inputs_make_labeled_unitinput(pan,item[0],item[1])
siz.Add(it)
pan.SetSizer(siz)
notebook.AddPage(pan,tab[0])
sizer.Add(notebook)
return sizer
def gui_inputs_make_labeled_unitinput(self,par,label='',units='',validatenumber=False):
sizer=wx.BoxSizer(wx.HORIZONTAL)
lbl=wx.StaticText(par,label=label+':')
sizer.Add(lbl)
edit=wx.TextCtrl(par)
sizer.Add(edit)
if units!=None:
unit=wx.StaticText(par,label=units)
sizer.Add(unit)
return sizer
app = wx.App(False)
frame = MainWindow("GUI")
app.MainLoop()
and this is a screenshot of what I get:
The following are ways I was able to get the black rectangles to disappear (but none let me do what I need to do):
When I remove the code to put the units in, it works. When I remove the text from the units fields, it works. When I have only one tab, it works.
If anyone can tell me why this is happening, I would be greatly appreciative.
Thanks in advance!
This was an interesting one:
The black boxes in the TextCtrl instances disappear as soon the sizer cascades are set up properly. You can do this by updating the layout of your main sizer by:
sizer.Layout()
or
sizer.Fit(self)
at the end of your make_gui method.
This works for me but does of course not explain why the TextCtrl stays black in the first place without.
I am using wxPython to make a gui. Currently I have a menubar, and three panels. I want to have a grid show up in the second panel when I click a button. However. When I click on the button, all I get is a small grey rectangle.
Here is the code for the button:
self.Bind(wx.EVT_BUTTON, self.OnCo, id=self.submit.GetId())
and here is the code for the "OnCo" event when the button is clicked:
def OnCo(self, e):
#to get rid of stuff that was previously in the panel
for child in self.panel2.GetChildren():
child.Destroy()
for child in self.panel3.GetChildren():
child.Destroy()
mygrid = gridlib.Grid(self.panel2, -1)
mygrid.CreateGrid(500,7)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(mygrid, -1, wx.EXPAND)
self.panel2.SetSizer(sizer)
mygrid.SetColLabelValue(0, 'S')
mygrid.SetColLabelValue(1, 'PB')
mygrid.SetColLabelValue(2, 'P')
mygrid.SetColLabelValue(3, 'T')
mygrid.SetColLabelValue(4, 'D')
Any help on how I can get my grid to show? Thanks.
It's possible that the grid is not sizing correctly; your items may be there, but it's not showing everything. After changing values in a grid, I always make sure to update the sizing of it. I usually just add a simple function to the class like this:
def SetGridSize(self):
self.mygrid.AutoSizeRows()
self.mygrid.AutoSizeColumns()
self.sizer.Fit(self)
and then call SetGridSize() whenever I change the values to make sure the whole thing shows on the screen instead of getting cut off.
Of course, you'll have to adapt it a bit to your names and whatnot. This implementation assumes the class is a wx.Frame object.
Is it possible to make a wxPython window only re-sizable to a certain ratio? I know you can disable resizing; however, I'd like it so when the window was resized it stuck to a certain width to height ratio.
One obvious way to do this would be to bind wx.EVT_SIZE to a function that constrains the aspect ratio. I'm not certain this is The Right Way to do this, but it works:
import wx
class SizeEvent(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Centre()
self.SetSizeWH(400, 300)
self.Show(True)
def OnSize(self, event):
hsize = event.GetSize()[0] * 0.75
self.SetSizeHints(minW=-1, minH=hsize, maxH=hsize)
self.SetTitle(str(event.GetSize()))
app = wx.App()
SizeEvent(None, 1, 'sizeevent.py')
app.MainLoop()
(The boilerplate is borrowed from here.)
I'm not too familiar with wxPython, but can't you just reset the window size to your max/min size that you want once the user pass that? Preferably in the event that detects resizing?
I am writing an app in python using getk and I've come across a problem. I am using an hbox to hold the buttons that go on my tool bar (new, open, save, etc) but the hbox seems to be expanding to take up a particular portion of the screen and the buttons are doing so as well making them really tall and ugly. How do I get them to stay at adecent size? I have already tried setting the expand and fill values to false and I can't find any methods to control the size of ether a button or an hbox. Here is the code for my new button:
img = gtk.Image()
img.set_from_stock(gtk.STOCK_NEW,gtk.ICON_SIZE_SMALL_TOOLBAR)
newButt = gtk.Button()
newButt.set_image(img)
newButt.show()
self.hBox4.pack_start(newButt, False, False, 0)
You need to contain the entire HBox in a VBox, as follows:
main = gtk.VBox(False)
...
main.pack_start(self.hbox4, False)
The reason for this is that gtk allows for window resizing, so it doesn't allocate fixed heights and widths. By using a VBox, you are telling Gtk that I want the HBox to take up just as much space as it needs at the top of my window.
Hope that helps.
Child widgets will assume the allocation size that they parent widgets can give. You should set the size of the hbox or the button.
See here the size_request method of PyGTK to get more information.
import wx
class TestDraw(wx.Panel):
def __init__(self,parent=None,id=-1):
wx.Panel.__init__(self,parent,id)
self.SetBackgroundColour("#FFFFFF")
self.Bind(wx.EVT_PAINT,self.onPaint)
def onPaint(self, event):
event.Skip()
dc=wx.PaintDC(self)
dc.BeginDrawing()
width=dc.GetSize()[0]
height=dc.GetSize()[1]
if height<width:
self.drawTestRects(dc)
else:
dc.Clear()
dc.EndDrawing()
def drawTestRects(self,dc):
dc.SetBrush(wx.Brush("#000000",style=wx.SOLID))
dc.DrawRectangle(50,50,50,50)
dc.DrawRectangle(100,100,100,100)
class TestFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(640,480))
self.mainPanel=TestDraw(self,-1)
self.Show(True)
app = wx.App(False)
frame = TestFrame(None,"Test App")
app.MainLoop()
This code should draw the test rectangles only when the height is less than the width, and otherwise the window should remain clear. However, if you mess with resizing the window, the panel isn't actually redrawn unless it is moved off the window. What am I doing wrong?
You can bind a method to handle wx.EVT_SIZE or the panel and invalidate it there. Alternatively simply use the wx.FULL_REPAINT_ON_RESIZE for the panel.
The documentation for a SizeEvent claims that there may be some complications when drawing depends on the dimensions of the window. I do not know exactly what is going on behind the scenes. I followed the suggestion on the link and added the call self.Refresh() to the top of onPaint() and this seems to give the desired behavior. See mghie's answer for a more efficient example of working code.