Inserting Image ruins the frame layout - python

I am starting using wx for my Python application and I run in a small problem when trying to include an image in a Frame.
I compiled this into a simple example.
What I dont understand is that I have no problem of layout when replacing my image by text. Whe I try entering the image, the layout gets ruined.
Here is my layout with text, that contains only two elements. The logo, and the title of the application
And here is what I got when trying to insert the logo :
For some reason, the layout is ruined, and the title is placed on top left corner (and we can't see it anymore.)
Here is my code :
#!/usr/bin/env python
import wx
class IvolutionWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(200, 100))
self.panel = wx.Panel(self)
# Creating the title layout
title = self.setup_titlelayout()
# Creating the main grid
maingrid = self.setup_maingrid(title)
self.panel.SetSizer(maingrid)
self.panel.Layout()
self.Show(True)
def setup_titlelayout(self):
hbox = wx.BoxSizer(wx.HORIZONTAL) # used to contain logo part and text part
vbox = wx.BoxSizer(wx.VERTICAL) # used to separate title and one-liner
logobox = wx.BoxSizer(wx.HORIZONTAL)
wx_logo = wx.EmptyBitmap(1, 1) # Create a bitmap container object.
wx_logo.LoadFile("ivolution/data/media/vitruve_50.jpg", wx.BITMAP_TYPE_ANY) # Load it with a file image.
#logo = wx.StaticBitmap(self, 1, wx_logo)
logo = wx.StaticText(self.panel, label="Logo Here") # Change for proper logo
title = wx.StaticText(self.panel, label="Ivolution")
logobox.Add(logo)
vbox.Add(title, flag=wx.RIGHT, border=8)
hbox.Add(logobox, flag=wx.RIGHT, border=8)
hbox.Add(vbox, flag=wx.RIGHT, border=8)
return hbox
def setup_maingrid(self, title):
maingrid = wx.FlexGridSizer(4, 1, vgap=0, hgap=0)
maingrid.Add(title)
return maingrid
def on_exit(self, event):
self.Close(True) # Close the frame.
if __name__ == "__main__":
app = wx.App(False)
frame = IvolutionWindow(None, "Ivolution")
app.MainLoop() # Runs application
It should run on any computer if you change the location of the image to a correct one.
The only line of code changing between the two pictures is here :
#logo = wx.StaticBitmap(self, 1, wx_logo)
logo = wx.StaticText(self.panel, label="Logo Here") # Change for proper logo
I am new with wxPython, so I guess there is something obvious here, but I can't find what.

You should use the same parent for the StaticText and the StaticBitmap if you want the same effect.
Thus use:
logo = wx.StaticBitmap(self.panel, 1, wx_logo)

Related

Overlapping widgets (controls) within wxPython BoxSizer

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.

wxpython - Erase background erases non-background components

In wxpython, I want to have a window with a picture that changes based on use of toolbar buttons with text controls on top of the picture. When I click the toolbar buttons, I am posting an erase background event, then capturing the erase event, and redrawing the new background from there (base on this).
Mostly works well, except that the text controls cease to be drawn once I redraw the background. They're still there, just not drawn.
Here is a simplified code that demonstrates the problem. If you run this code and click the button to toggle drawing the background image or not, the text controls disappear.:
import wx
import wx.lib.inspection
class PanelWithDrawing(wx.Panel):
def __init__(self, parent):
super(PanelWithDrawing, self).__init__(parent, size=(100, 40))
self.showbmp = False
self.txt = wx.TextCtrl(self, pos=(10, 10))
def onErase(self, dc):
if self.showbmp:
# dc.DrawBitmap(wx.Bitmap('background.png', 0, 0)
dc.DrawRectangle(0, 0, 40, 40) # use a drawing instead so you don't have to find a png
class Toolbar(wx.ToolBar):
def __init__(self, parent):
super(Toolbar, self).__init__(parent, -1)
self.AddLabelTool(wx.ID_SAVE, "Record", wx.Bitmap("picture.png", wx.BITMAP_TYPE_ANY), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
class Example(wx.Frame):
def __init__(self, parent, title):
super(Example, self).__init__(parent, title=title)
self.toolbar = Toolbar(self)
self.SetToolBar(self.toolbar)
self.toolbar.Realize()
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.panel1 = PanelWithDrawing(self.panel)
vbox.Add(self.panel1)
# self.panel2 = PanelWithText(self.panel)
# vbox.Add(self.panel2)
self.panel.SetSizer(vbox)
self.Centre()
self.Show()
self.toolbar.Bind(wx.EVT_TOOL, self.onButton)
self.panel1.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
def onErase(self, evt):
try:
dc = evt.GetDC()
except:
dc = wx.ClientDC(self)
rect = self.GetUpdateRegion().GetBox()
dc.SetClippingRect(rect)
dc.Clear()
self.panel1.onErase(dc)
def onButton(self, evt):
self.panel1.showbmp = not self.panel1.showbmp
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND))
if __name__ == '__main__':
app = wx.App()
Example(None, title='Example')
wx.lib.inspection.InspectionTool().Show() # use this for debugging GUI design
app.MainLoop()
How do I tell wxpython to draw all the non-background stuff again? Alternatively, how do I not un-draw it in the first place?
After working on it for a few days, I got it! And the answer is trivially simple (as usual).
wx.PostEvent(self.panel1, wx.PyCommandEvent(wx.wxEVT_ERASE_BACKGROUND)) should be replaced with self.Refresh() to refresh the whole frame and not just force a specific (and apparently unsafe) redraw.

wxpython rearrange bitmap on resize

I have this wxpython code, where I am displaying image along with text. I am using flexgridsizer for layout management. I want when I resize the window, images should reshuffle as per the size like if there is more room than the column and row should expand, also I see flicker while regenerating images.
Please suggest, if there is anyother better way to do this
import wx
ID_MENU_REFRESH = wx.NewId()
#dummy description of image
imglist =['One','Two','Three','Four','Five','Six','Seven','Eight','Nine','Ten']
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
mb = wx.MenuBar()
fMenu = wx.Menu()
fMenu.Append(ID_MENU_REFRESH, 'Refresh')
mb.Append(fMenu, '&Action')
self.SetMenuBar(mb)
self.Bind(wx.EVT_MENU, self.refreshApps, id=ID_MENU_REFRESH)
#storing the thumb image in memory
self.bmp=wx.Image('img/myimg.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap()
self.panelOne = wx.Panel(self)
sizer = wx.BoxSizer(wx.HORIZONTAL)
self.panelOne.SetSizer(sizer)
self.panelOne.Layout()
self.myimg_holder={}
self.showThumb(imglist)
self.SetTitle('Example')
self.Centre()
self.Show(True)
def refreshApps(self,event=None):
#remove
self.showThumb()
#repaint
self.showThumb(imglist)
def showThumb(self,thumblist=None):
if not thumblist:
for child in self.panelOne.GetChildren():
child.Destroy()
self.myimg_holder.clear()
self.panelOne.Layout()
return
vbox = wx.BoxSizer(wx.VERTICAL)
hbox = wx.BoxSizer()
gs = wx.FlexGridSizer(8, 6, 10, 20)
#blank text holder for padding
gs.Add(wx.StaticText(self.panelOne),flag=wx.ALL|wx.EXPAND, border=2)
vzis = []
for num,app in enumerate(thumblist):
vzis.append(wx.BoxSizer(wx.VERTICAL))
appid = wx.StaticBitmap(self.panelOne,wx.ID_ANY, self.bmp, (5,5), (self.bmp.GetWidth()+5, self.bmp.GetHeight()),name=app.strip())
vzis[num].Add(appid,0, wx.ALIGN_CENTER)
self.myimg_holder[appid]=app
vzis[num].Add(wx.StaticText(self.panelOne,-1,app),0, wx.ALIGN_CENTER,border=1)
for i in range(len(thumblist)):
if i in [4,8,12]:
gs.Add(wx.StaticText(self.panelOne),flag=wx.ALL, border=2)
gs.Add(wx.StaticText(self.panelOne),flag=wx.ALL, border=2)
gs.Add(vzis[i],flag=wx.ALL, border=1)
else:
gs.Add(vzis[i],flag=wx.ALL, border=1)
vbox.Add(wx.StaticText(self.panelOne),flag=wx.ALL, border=4)
vbox.Add(gs, proportion=1, flag=wx.ALL)
vbox.Add(wx.StaticText(self.panelOne),flag=wx.ALL, border=4)
self.panelOne.SetSizer(vbox)
self.panelOne.Layout()
def main():
ex = wx.App()
frame = Example(None)
frame.Show()
ex.MainLoop()
if __name__ == '__main__':
main()
If you want the images to rearrange themselves, then you don't want to use the FlexGridSizer. What you want is the WrapSizer:
http://wxpython.org/Phoenix/docs/html/WrapSizer.html
http://www.blog.pythonlibrary.org/2014/01/22/wxpython-wrap-widgets-with-wrapsizer/
If you want the bitmaps themselves to resize when you're resizing the frame, then you'll have to do that yourself. The sizers don't do that automatically. They do resize normal widgets, but not images. You would probably need to catch EVT_SIZE and resize the bitmaps as appropriate if you wanted to go that route.
To reduce flicker, you'll probably want to take a look at the Freeze and Thaw methods.

image won't show even after image.Show()

Code below.
If you execute the program as is (just change "moresco.jpg" to any image on your computer), it will first show a black square, and if you click on the search button the image you hardcoded (moresco.jpg in my case) will be displayed.
What I want is to hide the black square at startup and show moresco.jpg when I click on search. So I thought of putting a .Show() over there.
If you uncomment line 22, the black square doesn't show (which is what we want), but then when you click on search moresco.jpg doesn't show.
If you have any suggestions on how to fix this code I would be grateful !
import wx
class gui(wx.Panel):
def __init__(self,parent):
self.parent=parent
wx.Panel.__init__(self,parent)
vsizer = wx.BoxSizer(wx.VERTICAL)
hsizer1 = wx.BoxSizer(wx.HORIZONTAL)
button = wx.Button(self,-1,"search")
self.Bind( wx.EVT_BUTTON,self.display,button)
hsizer1.Add(button,.1,wx.EXPAND)
vsizer.Add(hsizer1,.1,wx.EXPAND)
hsizer2 = wx.BoxSizer(wx.HORIZONTAL)
vsizer.Add(hsizer2,1,wx.EXPAND)
self.pnl=wx.Panel(self)
img = wx.EmptyImage(500,500)
self.imageCtrl = wx.StaticBitmap(self.pnl, wx.ID_ANY,
wx.BitmapFromImage(img))
# uncomment this line and the image won't show even after
# click on search button
#-----------------------------
# print self.imageCtrl.Hide()
#-----------------------------
hsizer3 = wx.BoxSizer(wx.HORIZONTAL)
hsizer3.Add(self.pnl,2,wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL,wx.EXPAND)
vsizer.Add(hsizer3,2,wx.EXPAND)
self.SetSizer(vsizer)
self.pnl.Layout()
def display(self,strip):
self.Refresh()
self.Update()
self.imageCtrl.Refresh()
self.imageCtrl.Update()
print self.imageCtrl.Show()
self.imageCtrl.Refresh()
self.imageCtrl.Update()
self.Refresh()
self.Update()
imageFile = "moresco.jpg"
jpg1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY)
# bitmap upper left corner is in the position tuple (x, y) = (5, 5)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(jpg1))
self.Refresh()
self.Update()
if __name__ == "__main__":
app = wx.App()
w,h=wx.DisplaySize()
frame = wx.Frame(parent=None, id=-1, title="transmorgripy",size=(w/1.2,h/1.2 ))
frame.Center()
panel = gui(frame)
frame.Show()
app.MainLoop()
Using Hide() and Show() on a control does not simply set it to being transparent or not. When it is hidden it does not have a place in its parent panel's Sizer. After you show the image control, it needs a chance to be fit into the parent panel. Depending on exactly how you want it to be displayed you may want to call Fit or Layout.
To show the image and trigger the Sizer giving it a position you could do something like this:
def display(self, strip):
print self.imageCtrl.Show()
imageFile = "moresco.jpg"
jpg1 = wx.Image(imageFile, wx.BITMAP_TYPE_ANY)
# bitmap upper left corner is in the position tuple (x, y) = (5, 5)
self.imageCtrl.SetBitmap(wx.BitmapFromImage(jpg1))
self.Layout()

Creating ScrolledWindow in wxPython

I am trying to make a ScrolledWindow that can scroll over a grid of images, but the scrollbar isn't appearing. wxWidgets documentation says:
The most automatic and newest way [to set the scrollbars in wxScrolledWindow] is to simply let sizers determine the scrolling area. This is now the default when you set an interior sizer into a wxScrolledWindow with wxWindow::SetSizer. The scrolling area will be set to the size requested by the sizer and the scrollbars will be assigned for each orientation according to the need for them and the scrolling increment set by wxScrolledWindow::SetScrollRate
So I try to set the sizer of my ScrolledWindow with a GridSizer but it's not working. The code:
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
menuBar = wx.MenuBar()
menu1 = wx.Menu()
m = menu1.Append(wx.NewId(), "&Blah", "Show Pictures")
menuBar.Append(menu1,"&Blah")
self.Bind(wx.EVT_MENU,self.OnInit,m)
self.SetMenuBar(menuBar)
def OnInit(self, event):
sizer = wx.GridSizer(rows=7,cols=3)
filenames = []
for i in range(20):
filenames.append("img"+str(i)+".png")
for fn in filenames:
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
sizer.Add(wx.StaticBitmap(self.panel,wx.ID_ANY,wx.BitmapFromImage(img)))
self.panel.SetSizer(sizer)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(parent=None,title="Frame")
self.frame.Show()
self.SetTopWindow(self.frame)
return True
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
Insert this
self.panel.SetScrollbars(1, 1, 1, 1)
after self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
If you want some info on the SetScrollBars method then look at this wxwidgets documentation page

Categories