I have MyPanel of size (1200,800) which is embedded in a parent MainPanel (whose size can change with the size of the MainFrame) :
import wx
class MyPanel(wx.Panel): # panel embedded in the main panel
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1, size=(1200,800))
sizer = wx.BoxSizer(wx.VERTICAL)
bmp = wx.BitmapFromImage(wx.Image('background.png', wx.BITMAP_TYPE_PNG))
myimg = wx.StaticBitmap(self, -1, bmp)
sizer.Add(myimg, 0, wx.SHAPED, 10)
class MainPanel(wx.Panel): # main panel embedded in the main frame
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.mypanel = MyPanel(self)
class MainFrame(wx.Frame): # main frame window
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(800,600))
self.panel = MainPanel(self)
self.Show()
app = wx.App(0)
frame = MainFrame(None, 'Test')
app.MainLoop()
How is it possible to automatically rescale MyPanel so that it fits in the parent MainPanel , keeping its aspect ratio ?
Remark : I am looking for a behaviour close to Windows's standard photo viewer : when the window is resized, the image is rescaled to fit in the parent window.
Just apply my previous answer to the panel that you want to maintain the aspect ration on. Not a 100% clear on the exact behavior you want but this should get you close enough.
import wx
class MyPanel(wx.Panel): # panel embedded in the main panel
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
txt = wx.StaticText(self, label="Missing Bitmap");
sizer.Add(txt, 0, wx.SHAPED, 10)
self.SetInitialSize((1200, 800))
self.BackgroundColour = wx.RED
self.Sizer = sizer
self.Bind(wx.EVT_SIZE, self.OnSize)
def OnSize(self, evt):
hsize = evt.Size[0] * 0.75 # Constrain max height to 75% of width
self.SetSizeHints(-1, hsize, maxH=hsize)
evt.Skip()
Important part is here, you need to adjust the size hints on the sub panel window every time its size is being requested to change to tell the sizer to limit the constraints on the geometry of the window.
class MainPanel(wx.Panel): # main panel embedded in the main frame
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
self.mypanel = MyPanel(self)
self.BackgroundColour = wx.BLACK
Background color set to help show difference between parent panel and its child panel which is colored red.
vsizer = wx.BoxSizer(wx.VERTICAL)
vsizer.AddStretchSpacer(0)
vsizer.Add(self.mypanel, 1, wx.SHAPED|wx.EXPAND|wx.ALIGN_CENTER)
vsizer.AddStretchSpacer(0)
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.AddStretchSpacer(0)
hsizer.Add(vsizer, 1, wx.EXPAND|wx.ALIGN_CENTER)
hsizer.AddStretchSpacer(0)
self.Sizer = hsizer;
Here in the MainPanel it uses two sizers with stretch spacers to keep the subpanel pushed towards the middle both horizontally and vertically as the window resizes.
class MainFrame(wx.Frame): # main frame window
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title, size=(800,600))
self.panel = MainPanel(self)
self.Show()
app = wx.App(0)
frame = MainFrame(None, 'Test')
app.MainLoop()
You need to be using sizers for this type of thing, and if you are then using wx.SHAPED in the sizer.Add flags will maintain the aspect ratio as the size is changed.
Related
I'm adding elements to a horizontal wx.BoxSizer, but instead of being layed-out next to each other, they are displayed on top of each other (all are placed in pixel position (0,0) of the parent panel).
Below are shortened versions of the files (the relevant parts):
main.py:
import wx
from frm_users import UsersForm
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "My App Title", size=(1200, 800))
self.panel = wx.Panel(self, wx.ID_ANY)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusWidths([-1, 60])
# this event is bound to a menu item I construct elsewhere
def onUsers(self, event=None):
frmUsers = UsersForm(self.panel)
if __name__ == "__main__":
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
frm_users.py:
from dbmodel import OlvUsers, Users
import forms_controller
import wx
class UsersForm(wx.Panel):
def __init__(self, parent):
# parent here is the panel
toolbox = forms_controller.getToolboxSizer(self, parent)
parent.SetSizer(toolbox)
def onSearch(self, event=None):
print("Searching")
forms_controller.py:
import wx
def getToolboxSizer(parent, frame):
# frame is a panel usually
toolboxSizer = wx.BoxSizer(wx.HORIZONTAL)
toolboxSizer.AddSpacer(5)
font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
# create the search related widgets
searchByLbl = wx.StaticText(frame, label="Search By:")
searchByLbl.SetFont(font)
toolboxSizer.Add(searchByLbl, 0, wx.ALL, 5)
cat = ["Author", "Title", "ISBN", "Publisher"]
categories = wx.ComboBox(frame, value="Author", choices=cat)
toolboxSizer.Add(categories, 0, wx.ALL, 5)
search = wx.SearchCtrl(frame, style=wx.TE_PROCESS_ENTER)
search.Bind(wx.EVT_TEXT_ENTER, parent.onSearch)
toolboxSizer.Add(search, 0, wx.ALL, 5)
return toolboxSizer
What am I missing?
In main.py, the instance of UsersForm is not inside of a sizer. Panels do not fill their parent automatically except when they are the sole child of a wx.Frame. To make this work, you should add frmUsers to a sizer that is associated with the panel in the frame.
I think there may also be an issue in getToolboxSizer since the widgets there appear to be getting added directly to the frame instead of a panel. You usually want to add child widgets to a Panel so that tabbing will work correctly.
I would probably change onUsers to the following:
def onUsers(self, event=None):
child_sizer = getToolboxSizer(self.panel)
self.main_sizer.Add(child_sizer, 0, wx.ALL, 5)
Then update getToolboxSizer so it doesn't place all its widgets on the parent since the parent will now be a panel.
I'm creating a wx.agw.SuperToolTip. I'm updating the message in the tip every few seconds, and if the tip is showing when the message updates the tip is redrawn in a different position.
The new position seems to be relative to the original position's relation to the top left corner of the screen, but that could just be coincidence.
Also, if I modify wx.lib.agw.supertooltip.ToolTipWindowBase.Invalidate() by commenting out the call to self.CalculateBestSize() the problem goes away. Of course then the window won't resize, so that's no solution.
I'm using wxPython 2.8.12.1.
Here's an app that demonstrates the problem:
class MyFrame(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, -1, title,
pos=(150, 150), size=(350, 225))
panel = wx.Panel(self)
btn = wx.Button(panel, -1, "Hover over this")
self._superTip = SuperToolTip("")
self._superTip.SetHeader("Heyo!")
self._superTip.SetTarget(btn)
self._superTip.EnableTip(True)
self._superTip.SetDrawHeaderLine(True)
self._superTip.SetDrawFooterLine(True)
self._superTip.SetStartDelay(1)
self._superTip.SetEndDelay(60)
currentFooterFont = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
currentFooterFont.SetPointSize(6)
currentFooterFont.SetWeight(wx.NORMAL)
self._superTip.SetFooterFont(currentFooterFont)
self._superTip.SetFooter('(Click to close)')
self._superTip.ApplyStyle("Blue Glass")
self._superTip.SetDropShadow(True)
self.ttTimer = wx.Timer(self)
self.ttText = 'What the?'
self.Bind(wx.EVT_TIMER, self.onTimer, self.ttTimer)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(btn, 0, wx.ALL, 10)
panel.SetSizer(sizer)
self.ttTimer.Start(2000)
panel.Layout()
def onTimer(self, evt):
self._superTip.SetMessage(self.ttText)
self.ttText += '?'
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, "STT error demo")
self.SetTopWindow(frame)
frame.Show(True)
return True
app = MyApp(redirect=True)
app.MainLoop()
Any thoughts on how I can update a visible tooltip without its location changing?
Thanks a lot.
I'm trying to make a form that has several input fields. Underneath these fields I want to have a wxpython Ultimate List Control (for all intents and purposes it's the same thing as a List Control). My issue is with sizers. To give some context, my form looks like this
Name [TextCtrl]
Blah [TextCtrl]
ListControl
I want it to look like
Name [TextCtrl]
Blah [TextCtrl]
ListCtrl (this spans to the end of the row)
My problem is when I try to add the List Control. I want the list control to Stretch from The Static Text to the Text Control, but it pushes the TextControl over. Can someone please point me in the right direction? I have attached the relevant code below.
class UserField(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent=parent, title="Info", size=(350, 400),
style=wx.DEFAULT_FRAME_STYLE)
self.init_ui()
self.Center()
self.ShowModal()
def init_ui(self):
panel = wx.Panel(self, wx.ID_ANY)
hbox = wx.BoxSizer(wx.VERTICAL)
flex_grid = wx.FlexGridSizer(5, 2, 5, 10) # row, col, vgap, hgap
info_text = wx.StaticText(parent=panel, label="Enter information")
self.search_button = wx.Button(parent=panel, label="Search")
self.list_control = UltimateListCtrl(panel,
agwStyle=wx.LC_REPORT | wx.BORDER_SUNKEN | ULC_HAS_VARIABLE_ROW_HEIGHT, )
flex_grid.AddMany(
[
info_text, self.search_button
]
)
lbox = wx.BoxSizer(wx.HORIZONTAL)
lbox.Add(self.list_control
hbox.Add(flex_grid, wx.EXPAND|wx.ALL)
hbox.Add(lbox, proportion=1, flag=wx.ALL|wx.EXPAND)
panel.SetSizer(hbox)
Here's a quick demonstration of wx.GridBagSizer. The program opens a simple frame with a single button that spawns a dialog with a GridBagSizer. You can place items in the sizer according to a position (pos) and optionally allow a widget to span multiple rows and/or columns (span).
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.SetSize((300, 200))
self.Centre()
self.Show(True)
self.InitUI()
def InitUI(self):
panel = wx.Panel(self)
sizer = wx.BoxSizer()
btn = wx.Button(panel, label="Spawn Window")
btn.Bind(wx.EVT_BUTTON, self.spawn_window)
sizer.Add(btn)
panel.SetSizerAndFit(sizer)
def spawn_window(self, evt):
UserField(self)
def OnQuit(self, e):
self.Close()
class UserField(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent=parent, title="Info", size=(350, 400),
style=wx.DEFAULT_FRAME_STYLE)
self.init_ui()
self.Center()
self.ShowModal()
def init_ui(self):
panel = wx.Panel(self)
sizer = wx.GridBagSizer(10, 10)
field1Label = wx.StaticText(panel, label="Field 1")
field2Label = wx.StaticText(panel, label="Field 2")
field1Ctrl = wx.TextCtrl(panel)
field2Ctrl = wx.TextCtrl(panel)
listCtrl = wx.ListCtrl(panel)
sizer.Add(field1Label, pos=(0, 0))
sizer.Add(field2Label, pos=(1, 0))
sizer.Add(field1Ctrl, pos=(0, 1))
sizer.Add(field2Ctrl, pos=(1, 1))
# HERE'S THE IMPORTANT LINE. NOTE THE 'span' ARGUMENT:
sizer.Add(listCtrl, pos=(2, 0), span=(1, 2), flag=wx.EXPAND)
panel.SetSizerAndFit(sizer)
if __name__ == '__main__':
ex = wx.App()
mainFrame = Example(None)
ex.MainLoop()
I know this is a very simple question but I've spent hours trying to figure out a solution. I want my panel to be a 50x50 blue box that doesn't change size, and stays centered in the window. I've tried having an inner and outer sizer that are HORIZONTAL and VERTICAL respectively and it seems to only recognize the outer sizer. I'd greatly appreciate it if anyone could help me out.
import wx
class Main(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Main',size=(700,500))
self.SetBackgroundColour('white')
panel = wx.Panel(self,wx.ID_ANY,size=(50,50))
panel.SetBackgroundColour('blue')
sizer=wx.BoxSizer()
sizer.Add(panel,0,wx.ALIGN_CENTER|wx.ALL)
self.SetSizer(sizer)
if __name__=='__main__':
app=wx.App()
frame=Main(parent=None, id=-1)
frame.Show()
app.MainLoop()
Adding a stretchspacer on either side of the panel will do it
import wx
class Main(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Main', size=(700, 500))
self.SetBackgroundColour('white')
panel = wx.Panel(self, wx.ID_ANY, size=(50, 50))
panel.SetBackgroundColour('blue')
sizer = wx.BoxSizer()
sizer.AddStretchSpacer(1)
sizer.Add(panel, 0, wx.ALIGN_CENTER)
sizer.AddStretchSpacer(1)
self.SetSizer(sizer)
if __name__ == '__main__':
app = wx.App()
frame = Main(parent=None, id=-1)
frame.Show()
app.MainLoop()
It seems to me that the following code should display text right in the centre of the window; that is, in the centre of the inner panel. It doesn't however, and I'm wondering why not. If you run the code, you'll see a white panel in the middle of the frame, 150px by 150px. I do not want this area to change in size at all, but when I go about adding some text (uncommenting the txt variable in the middle of the snippet)the panel invariably shrinks to fit the text. Even specifying the size of the StaticText to match the panel isn't a solution because the text doesn't then centre-align.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.rootPanel = wx.Panel(self)
innerPanel = wx.Panel(self.rootPanel,-1, size=(150,150), style=wx.ALIGN_CENTER)
innerPanel.SetBackgroundColour('WHITE')
hbox = wx.BoxSizer(wx.HORIZONTAL)
vbox = wx.BoxSizer(wx.VERTICAL)
# I want this line visible in the CENTRE of the inner panel
#txt = wx.StaticText(innerPanel, id=-1, label="TEXT HERE",style=wx.ALIGN_CENTER, name="")
hbox.Add(innerPanel, 0, wx.ALL|wx.ALIGN_CENTER)
vbox.Add(hbox, 1, wx.ALL|wx.ALIGN_CENTER, 5)
self.rootPanel.SetSizer(vbox)
vbox.Fit(self)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'wxBoxSizer.py')
frame.Show(True)
frame.Center()
return True
app = MyApp(0)
app.MainLoop()
You just need to add a couple spacers to make it work.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.rootPanel = wx.Panel(self)
innerPanel = wx.Panel(self.rootPanel,-1, size=(150,150), style=wx.ALIGN_CENTER)
innerPanel.SetBackgroundColour('WHITE')
hbox = wx.BoxSizer(wx.HORIZONTAL)
vbox = wx.BoxSizer(wx.VERTICAL)
innerBox = wx.BoxSizer(wx.VERTICAL)
# I want this line visible in the CENTRE of the inner panel
txt = wx.StaticText(innerPanel, id=-1, label="TEXT HERE",style=wx.ALIGN_CENTER, name="")
innerBox.AddSpacer((150,75))
innerBox.Add(txt, 0, wx.CENTER)
innerBox.AddSpacer((150,75))
innerPanel.SetSizer(innerBox)
hbox.Add(innerPanel, 0, wx.ALL|wx.ALIGN_CENTER)
vbox.Add(hbox, 1, wx.ALL|wx.ALIGN_CENTER, 5)
self.rootPanel.SetSizer(vbox)
vbox.Fit(self)
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'wxBoxSizer.py')
frame.Show(True)
frame.Center()
return True
app = MyApp(0)
app.MainLoop()