I am trying to create a wxPython frame that is just the size of the toolbar I created.
I have successfully created the wx.Frame and empty toolbar but the window is far too big, how do I make it just fit the toolbar.
Code I tried:
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
vbox = wx.BoxSizer(wx.VERTICAL)
toolbar1 = wx.ToolBar(self)
toolbar1.SetToolBitmapSize(wx.Size(64, 64))
toolbar1.Realize()
vbox.Add(toolbar1, 0, wx.EXPAND)
self.SetSizer(vbox)
self.SetTitle('Toolbars')
self.Centre()
vbox.Fit(self)
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
Is it not possible to set the size of frame to size of tool bar.
Something like.
self.size = toolbar1.size
I'm would assume that the reason that the toolbar size on linux/mac is (0, 0) is b/c there are no tools attached to it. To workaround that you can set a minimum size to the toolbar and then calculate the frame size by adding the frame caption and borders to the toolbar size.
import wx
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
vbox = wx.BoxSizer(wx.VERTICAL)
toolbar1 = wx.ToolBar(self)
toolbar1.SetBackgroundColour(wx.RED)
toolbar1.SetToolBitmapSize(wx.Size(64, 64))
# prevent the sizer from setting the size to (0, 0) when there are no tools attached
toolbar1.SetMinSize((64, 64))
toolbar1.Realize()
vbox.Add(toolbar1, 0, wx.EXPAND)
self.SetSizer(vbox)
self.SetTitle('Toolbars')
self.Centre()
vbox.Fit(self)
self.Layout()
# calculate the frame caption height
caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y, self)
# *2 for the left and right border
border_width = wx.SystemSettings.GetMetric(wx.SYS_BORDER_X, self) * 2
# get the size of the toolbar
sx, sy = toolbar1.GetSize()
# set the frame size by adding the toolbar size to the border/caption size
self.SetSize((sx + border_width, sy + caption_height))
app = wx.App()
ex = Example(None)
ex.Show()
app.MainLoop()
Related
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()
I am working with python v2.7 and wxPython v3.0 on Windows 8 OS.
In the code snippet provided below simply creates a frame which has a vertical sizer that contains an image and an other horizontal sizer that in turn contains two panels.
Question: When I resize the frame by dragging the border, the panels resize automatically. Unlike the panels the image doesn't resize? How can I make my image to behave like panels for the case of resizing?
Code: The image file used can be downloaded from here: green.bmp
import wx
class Frame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, style=wx.DEFAULT_FRAME_STYLE)
panelA = wx.Panel(self,-1)
imageFile = wx.Image('greenbig.bmp', wx.BITMAP_TYPE_ANY).ConvertToBitmap()
myBitmap = wx.StaticBitmap(panelA, -1, imageFile)
panel1 = wx.Panel(self, -1,style=wx.SIMPLE_BORDER)
panel2 = wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
panelSizer = wx.BoxSizer(wx.HORIZONTAL)
panelSizer.Add(panel1, 1, wx.ALL|wx.EXPAND, 0)
panelSizer.Add(panel2, 1, wx.ALL|wx.EXPAND, 0)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panelA, 1, wx.ALL|wx.EXPAND, 0)
sizer.Add(panelSizer, 1, wx.ALL|wx.EXPAND, 0)
self.SetSizer(sizer)
self.Show(True)
app = wx.App()
frame = Frame(None, wx.ID_ANY, 'Image')
app.MainLoop()
Thank you for your time!
(eight years later) I was struggling to show an image that was dynamically resized with the window frame. These days, I'm using Python 3 with wxPython version 4. I pieced this solution from a lot of reading of the documentation and tips on how best to use sizers. I then whittled my code down to the absolute minimum required to accomplish this narrow use case.
Notice that I don't ever call Frame.Layout() or Panel.Layout() because I rely on the initial size of the Frame and the fact that a single Panel in a Frame will be automatically resized with the Frame. Also, any call to Fit() was unnecessary. The sizer containing the ImagePanel does need the EXPAND flag, but the ImagePanel does not need a sizer itself.
Note: you'll have to supply your own example image to load.
import wx
class ImagePanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
self.img = wx.Image(width=1, height=1)
self.ctrl = wx.StaticBitmap(self, bitmap=self.img.ConvertToBitmap())
self.Bind(wx.EVT_SIZE, self.on_resize)
def load_image(self):
with open('image.png', 'rb') as fin:
self.img = wx.Image(fin)
self.update_bitmap()
def update_bitmap(self):
w, h = self.GetSize()
self.ctrl.SetBitmap(self.img.Scale(w, h).ConvertToBitmap())
def on_resize(self, evt):
self.update_bitmap()
class MainWindow(wx.Frame):
def __init__(self):
initial_size = wx.Size(width=480, height=480)
super().__init__(None, wx.ID_ANY, title='Main Window Title', size=initial_size)
self.panel = wx.Panel(self)
self.image = ImagePanel(self.panel)
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(self.image, proportion=1, flag=wx.EXPAND)
self.panel.SetSizer(vbox)
if __name__ == '__main__':
app = wx.App()
win = MainWindow()
win.Show()
win.image.load_image()
app.MainLoop()
The image will never be resized automatically. If you want this behaviour, you need to handle wxEVT_SIZE and do it yourself in its handler.
How do I mention size(350,450) in terms of percentages in wxpython?
# -*- coding: utf-8 -*-
# gotoclass.py
import wx
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title,
size=(390, 350))
self.InitUI()
self.Centre()
self.Show()
def InitUI(self):
panel = wx.Panel(self)
font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
font.SetPointSize(9)
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
st1 = wx.StaticText(panel, label='Class Name')
st1.SetFont(font)
hbox1.Add(st1, flag=wx.RIGHT, border=8)
tc = wx.TextCtrl(panel)
hbox1.Add(tc, proportion=1)
vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)
vbox.Add((-1, 10))
panel.SetSizer(vbox)
if __name__ == '__main__':
app = wx.App()
Example(None, title='Go To Class')
app.MainLoop()
Get the screen resolution using wx.GetDisplaySize(). Compute the percentage in pixels. Set the size by self.SetSize() later, not in the constructor but before self.Show().
You can only specify the size of widgets in wxPython by pixel size. See http://wxpython.org/docs/api/wx.Size-class.html for more information. You cannot pass percentages to it. On the other hand, you can sort of do percentages with widgets inside sizers as sizers support the idea of proportion. But I don't believe that is what you really want.
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.
I'm building an app that should contain a resizable sidebar area on the left (starting at 200px width) and a main area on the right that should expand to fill the remaining area. I've gone with the SplitterWindow method as it's the only one I think offers manual resize on panels. I'm experiencing some black borders around the individual panels AND the entire frame that I can't seem to get rid of. The borders on the individual panels disappear when I comment out the make_canvas calls, but the border on the frame is still there. Strangely enough if I resize the entire app window the borders flicker on and off. I suspect it's not actually a border issue but a BoxSizing issue, but I'm not sure how to take care of it.
Here's the code:
#! /usr/bin/python
# -*- coding: utf-8 -*-
import wx, random
class TDTaskBarIcon(wx.TaskBarIcon):
def __init__(self, parent):
wx.TaskBarIcon.__init__(self)
self.parentApp = parent
self.icon = wx.Icon("images/icon_glasses.png", wx.BITMAP_TYPE_PNG)
self.SetIconImage()
def SetIconImage(self):
self.SetIcon(self.icon)
class Sidebar(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# tiled background
self.bgimage = wx.Bitmap('images/noise.png')
wx.FutureCall(50, self.make_canvas)
wx.EVT_SIZE(self, self.make_canvas)
self.SetBackgroundColour((229,226,218))
def make_canvas(self, event=None):
dc = wx.ClientDC(self)
brush_bmp = wx.BrushFromBitmap(self.bgimage)
dc.SetBrush(brush_bmp)
w, h = self.GetClientSize()
dc.DrawRectangle(0, 0, w, h)
class Main(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
# tiled background
self.bgimage = wx.Bitmap('images/noise.png')
wx.FutureCall(50, self.make_canvas)
wx.EVT_SIZE(self, self.make_canvas)
self.SetBackgroundColour((229,226,218))
self.SetBackgroundColour('WHITE')
def make_canvas(self, event=None):
dc = wx.ClientDC(self)
brush_bmp = wx.BrushFromBitmap(self.bgimage)
dc.SetBrush(brush_bmp)
w, h = self.GetClientSize()
dc.DrawRectangle(0, 0, w, h)
# Create Tapedeck class
class Tapedeck(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
self.tbicon = TDTaskBarIcon(self)
self.tbicon.Bind(wx.EVT_MENU, self.OnQuit, id=wx.ID_EXIT)
splitter = wx.SplitterWindow(self)
self.Sidebar = Sidebar(splitter)
self.Main = Main(splitter)
splitter.SplitVertically(self.Sidebar, self.Main)
splitter.SetSashPosition(200)
splitter.SetMinimumPaneSize(200)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(splitter, 1, wx.EXPAND)
self.SetSizerAndFit(sizer)
self.SetAutoLayout(True)
self.InitUI()
self.SetSize((800, 600))
self.SetTitle('Tapedeck')
self.Center()
self.Show(True)
def InitUI(self):
panel = wx.Panel(self)
# font styles
header = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD, False, u'Helvetica')
# create a menubar at the top of the user frame
menuBar = wx.MenuBar()
# create menus
fileMenu = wx.Menu()
helpMenu = wx.Menu()
# export
export = fileMenu.Append(wx.NewId(), "&Export", "Export Playlist",
wx.ITEM_NORMAL)
export.SetBitmap(wx.Bitmap('images/men_playlist.png'))
fileMenu.AppendSeparator()
# quit
quit = fileMenu.Append(wx.NewId(), "&Quit\tCtrl+Q", "Quit the program",
wx.ITEM_NORMAL)
quit.SetBitmap(wx.Bitmap('images/men_quit.png'))
self.Bind(wx.EVT_MENU, self.OnQuit, quit)
# put the file menu on the menubar
menuBar.Append(fileMenu, "&File")
# about tapedeck
about = helpMenu.Append(wx.NewId(), "&About TapeDeck",
"About TapeDeck", wx.ITEM_NORMAL)
about.SetBitmap(wx.Bitmap('images/men_skull.png'))
self.Bind(wx.EVT_MENU, self.OnAbout, about)
# put the help menu on the menubar
menuBar.Append(helpMenu, "&Help")
# set menu bar
self.SetMenuBar(menuBar)
# create a status bar at the bottom of the frame
self.CreateStatusBar()
def OnQuit(self, e):
self.tbicon.RemoveIcon()
self.tbicon.Destroy()
self.Close()
def OnAbout(self, e):
self.SetStatusText("Here's your help!")
# Run the application
def main():
deck = wx.App()
Tapedeck(None)
deck.MainLoop()
if __name__ == '__main__':
main()
And screenshots:
Before resize (source):
After resize (source):
Suggestions?
You are drawing those lines with the call to DrawRectangle.
If you want to eliminate the lines and still draw the rectangle you can do:
dc.SetPen(wx.Pen("WHITE",1))
dc.DrawRectangle(0, 0, w, h)
in the two make_canvas methods. It works in windows.