I am trying to display a grid of images in wxPython. I am using a GridSizer to create a grid to which I add my staticbitmaps. But for some reason I only see one image at the first position in the grid. I am not sure where I am going wrong. Here is my code and the corresponding output.
import wx
import sys
import glob
MAIN_PANEL = wx.NewId()
class CommunicationApp(wx.App):
"""This is the class for the communication tool application.
"""
def __init__(self, config=None, redirect=False):
"""Instantiates an application.
"""
wx.App.__init__(self, redirect=redirect)
self.cfg = config
self.mainframe = CommunicationFrame(config=config,
redirect=redirect)
self.mainframe.Show()
def OnInit(self):
# self.SetTopWindow(self.mainframe)
return True
class CommunicationFrame(wx.Frame):
"""Frame of the Communication Application.
"""
def __init__(self, config, redirect=False):
"""Initialize the frame.
"""
wx.Frame.__init__(self, parent=None,
title="CMC Communication Tool",
style=wx.DEFAULT_FRAME_STYLE)
self.imgs = glob.glob('../img/img*.png')
self.panel = CommuniationPanel(parent=self,
pid=MAIN_PANEL,
config=config)
# # Gridbagsizer.
nrows, ncols = 3, 4
self.grid = wx.GridSizer(rows=nrows, cols=ncols)
# Add images to the grid.
for r in xrange(nrows):
for c in xrange(ncols):
_n = ncols * r + c
_tmp = wx.Image(self.imgs[_n],
wx.BITMAP_TYPE_ANY)
_temp = wx.StaticBitmap(self.panel, wx.ID_ANY,
wx.BitmapFromImage(_tmp))
self.grid.Add(_temp, 0, wx.EXPAND)
self.grid.Fit(self)
# set to full screen.
# self.ShowFullScreen(not self.IsFullScreen(), 0)
class CommuniationPanel(wx.Panel):
"""Panel of the Communication application frame.
"""
def __init__(self, parent, pid, config):
"""Initialize the panel.
"""
wx.Panel.__init__(self, parent=parent, id=pid)
# CALLBACK BINDINGS
# Bind keyboard events.
self.Bind(wx.EVT_KEY_UP, self.on_key_up)
def on_key_up(self, evt):
"""Handles Key UP events.
"""
code = evt.GetKeyCode()
print code, wx.WXK_ESCAPE
if code == wx.WXK_ESCAPE:
sys.exit(0)
def main():
app = CommunicationApp()
app.MainLoop()
if __name__ == '__main__':
main()
Here is the output.
What I would like to see (or expected to see) was 3X4 grid of the different images.
What is the problem with my code? And how do I fix it?
You appear to have forgotten to set a sizer for your panel
Try putting this in the frame's __init__
self.panel.SetSizer(self.grid)
Related
I am writing a program to play a video on the right panel when a button pressed on a left panel after selecting what to play. This acts as a test function to show the user. I am a beginner in using Python and WXPython. Learning on the go.
I have added a snippet of the code that I am stuck on below:
import wx, wx.media
filePathList = ["None", "None", "None", "None", "None"]
class FrameClass (wx.Frame):
def __init__(self, parent):
super(FrameClass, self).__init__(None, title = "Super Bot", size = (750, 400))
vsplitter = wx.SplitterWindow(self)
left = LeftPanel(vsplitter, self)
self.right = RightPanel(vsplitter, self)
vsplitter.SplitVertically(left, self.right)
vsplitter.SetMinimumPaneSize(200)
self.Show(True)
class LeftPanel (wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent = parent)
testBtn1 = wx.Button(self, -1, "Test", pos = (5, 20))
self.Bind(wx.EVT_BUTTON, self.buttonPressed1, testBtn1)
def buttonPressed1(self, event):
file0 = filePathList[0]
self.right.onTestClick(file0)
class RightPanel (wx.Panel):
def __init__(self, parent, media):
wx.Panel.__init__(self, parent = parent)
self.mediaFilePath = media
def onTestClick(self):
self.testMedia = wx.media.MediaCtrl(self, size = (500, 300), style=wx.SIMPLE_BORDER, szBackend = wx.media.MEDIABACKEND_WMP10)
self.testMedia.Bind(wx.media.EVT_MEDIA_LOADED, self.play)
self.testMedia.Load(self.mediaFilePath)
def play(self, event):
self.testMedia.Play()
Currently all works well. What apart from passing the video to the rightPanel video onTestClick. Where is shows the current error
Traceback (most recent call last):
File "frame1.py", line 151, in buttonPressed1
self.right.onTestClick(file0)
AttributeError: 'LeftPanel' object has no attribute 'right'
I can imagine that because right is defined in the FrameClass that it is not known about inside of the LeftPanel when trying to use it.
Any help would be appreciated.
If you want a quick fix, you can just call:
self.GetParent().right.onTestClick(file0)
which is kind-of ugly, because the the parent creates children
and children must know about the structure of the parent at
the same time.
Probably the most wxPython-ish solution would be to create your
own event, which would be created and triggered when the button
is pressed. This event would be handled in the FrameClass.
There is a nice intro here:
https://wxpython.org/Phoenix/docs/html/events_overview.html#custom-event-summary
Finally got it working. Thank you for your inputs as well. Most helpful.
I just needed to make the left panel aware of the right panel. In this case...
vsplitter = wx.SplitterWindow(self)
right = RightPanel(vsplitter, self)
left = LeftPanel(vsplitter, right)
vsplitter.SplitVertically(left, right)
vsplitter.SetMinimumPaneSize(200)
self.Show(True)
class LeftPanel (wx.Panel):
def __init__(self, parent, top):
wx.Panel.__init__(self, parent = parent)
self.refTop = top
and when I wanted to send it something, I reference "self.refTop". Such as...
def buttonPressed1(self, event):
file1 = filePathList[0]
self.refTop.onTestClick(file1)
making sure that RightPanel has an argument ready for it.
class RightPanel (wx.Panel):
def __init__(self, parent, media):
self.mediaFilePath = media
I'm currently learning to program with Python, I got this example code for a simple image viewer (see below), which produces the following error message when I close the app, run it again and open any directory to browse the images in it:
File "...\image_viewer4.py", line 103, in update_photo
self.image_ctrl.SetBitmap(wx.Bitmap(img))
RuntimeError: wrapped C/C++ object of type StaticBitmap has been
deleted.
This happens every time unless I restart the kernel in Spyder.
I already read through PyQt: RuntimeError: wrapped C/C++ object has been deleted, Understanding the “underlying C/C++ object has been deleted” error, and Can a PyQt4 QObject be queried to determine if the underlying C++ instance has been destroyed?, but I don't understand what causes the error in the code example above. I mean, the error states that the bitmap object is deleted but i have no clue as to how or why.
I appreciate any productive help anyone can give me. I'd like to understand what exactly causes the error in the code and how to avoid it.
Here is the code:
import glob
import os
import wx
from pubsub import pub
class ImagePanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
disW, disH = wx.DisplaySize()
self.max_size = 800
self.photos = []
self.current_photo = 0
self.total_photos = 0
self.layout()
pub.subscribe(self.update_photos_via_pubsub, "update")
def layout(self):
"""
Layout the widgets on the panel
"""
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
img = wx.Image(self.max_size, self.max_size)
self.image_ctrl = wx.StaticBitmap(self, wx.ID_ANY,
wx.Bitmap(img))
self.main_sizer.Add(self.image_ctrl, 0, wx.ALL|wx.CENTER, 5)
self.image_label = wx.StaticText(self, label="")
self.main_sizer.Add(self.image_label, 0, wx.ALL|wx.CENTER, 5)
btn_data = [("Previous", btn_sizer, self.on_previous),
("Next", btn_sizer, self.on_next)]
for data in btn_data:
label, sizer, handler = data
self.btn_builder(label, sizer, handler)
self.main_sizer.Add(btn_sizer, 0, wx.CENTER)
self.SetSizer(self.main_sizer)
def btn_builder(self, label, sizer, handler):
"""
Builds a button, binds it to an event handler and adds it to a sizer
"""
btn = wx.Button(self, label=label)
btn.Bind(wx.EVT_BUTTON, handler)
sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
def on_next(self, event):
"""
Loads the next picture in the directory
"""
if not self.photos:
return
if self.current_photo == self.total_photos - 1:
self.current_photo = 0
else:
self.current_photo += 1
self.update_photo(self.photos[self.current_photo])
def on_previous(self, event):
"""
Displays the previous picture in the directory
"""
if not self.photos:
return
if self.current_photo == 0:
self.current_photo = self.total_photos - 1
else:
self.current_photo -= 1
self.update_photo(self.photos[self.current_photo])
def update_photos_via_pubsub(self, photos):
self.photos = photos
self.total_photos = len(self.photos)
self.update_photo(self.photos[0])
self.current_photo = 0
def update_photo(self, image):
"""
Update the currently shown photo
"""
img = wx.Image(image, wx.BITMAP_TYPE_ANY)
# scale the image, preserving the aspect ratio
W = img.GetWidth()
H = img.GetHeight()
if W > H:
NewW = self.max_size
NewH = self.max_size * H / W
else:
NewH = self.max_size
NewW = self.max_size * W / H
img = img.Scale(NewW, NewH)
self.image_ctrl.SetBitmap(wx.Bitmap(img))
self.Refresh()
def reset(self):
img = wx.Image(self.max_size,
self.max_size)
bmp = wx.Bitmap(img)
self.image_ctrl.SetBitmap(bmp)
self.current_photo = 0
self.photos = []
class MainFrame(wx.Frame):
def __init__(self):
disW, disH = wx.DisplaySize()
super().__init__(None, title='Image Viewer',
size=(disW-500, disH-100))
self.panel = ImagePanel(self)
# create status bar
self.CreateStatusBar()
# create the menu bar
menuBar = wx.MenuBar()
# create a menu
fileMenu = wx.Menu()
openBut = wx.MenuItem(fileMenu, wx.ID_OPEN, '&Open', 'Open Working Directory')
fileMenu.Append(openBut)
fileMenu.Bind(wx.EVT_MENU, self.on_open_directory, openBut)
# add a line separator to the file menu
fileMenu.AppendSeparator()
# add a quit button to fileMenu
quitBut = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit', 'Exit the Programm')
fileMenu.Append(quitBut)
# connect the quit button to the actual event of quitting the app
fileMenu.Bind(wx.EVT_MENU, self.onQuit, quitBut)
# give the menu a title
menuBar.Append(fileMenu, "&File")
# connect the menu bar to the frame
self.SetMenuBar(menuBar)
self.create_toolbar()
self.Show()
def create_toolbar(self):
"""
Create a toolbar
"""
self.toolbar = self.CreateToolBar()
self.toolbar.SetToolBitmapSize((16,16))
open_ico = wx.ArtProvider.GetBitmap(
wx.ART_FILE_OPEN, wx.ART_TOOLBAR, (16,16))
openTool = self.toolbar.AddTool(
wx.ID_ANY, "Open", open_ico, "Open an Image Directory")
self.Bind(wx.EVT_MENU, self.on_open_directory, openTool)
self.toolbar.Realize()
def on_open_directory(self, event):
"""
Open a directory dialog
"""
with wx.DirDialog(self, "Choose a directory",
style=wx.DD_DEFAULT_STYLE) as dlg:
if dlg.ShowModal() == wx.ID_OK:
self.folder_path = dlg.GetPath()
photos = glob.glob(os.path.join(self.folder_path, '*.jpg'))
if photos:
pub.sendMessage("update", photos=photos)
else:
self.panel.reset()
def onQuit(self, event):
# get the frame's top level parent and close it
wx.GetTopLevelParent(self).Destroy()
if __name__ == '__main__':
app = wx.App(redirect=False)
frame = MainFrame()
app.MainLoop()
In case they are needed, here is the traceback:
File "...\image_viewer4.py", line 183, in on_open_directory
pub.sendMessage("update", photos=photos)
File "C:\Anaconda\lib\site-packages\pubsub\core\publisher.py", line 216, in sendMessage
topicObj.publish(**msgData)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 452, in publish
self.__sendMessage(msgData, topicObj, msgDataSubset)
File "C:\Anaconda\lib\site-packages\pubsub\core\topicobj.py", line 482, in __sendMessage
listener(data, self, allData)
File "C:\Anaconda\lib\site-packages\pubsub\core\listener.py", line 237, in __call__
cb(**kwargs)
File "...\image_viewer4.py", line 84, in update_photos_via_pubsub
self.update_photo(self.photos[0])
It isn't immediately obvious but I notice that you don't cancel your pubsub subscription.
Try:
def onQuit(self, event):
# get the frame's top level parent and close it
pub.unsubscribe(self.panel.update_photos_via_pubsub, "update")
wx.GetTopLevelParent(self).Destroy()
See if that makes a difference.
To ensure that onQuit is also executed when closing the application by using the X in the top right of the frame, use the following (in MainFrame) :
self.Bind(wx.EVT_CLOSE, self.onQuit)
That should cover all the bases.
That may be the same issue as in
Defining a wx.Panel destructor in wxpython
It may come from the fact that a deleted window, containing the pub.subscribe(), answers the pub.sendMessage().
To avoid this you can try to put in your method the following:
def update_photo(self, image):
if self.__nonzero__():
do something
...
you can see wx.PyDeadObjectError –> RuntimeError in following documentation:
https://wxpython.org/Phoenix/docs/html/MigrationGuide.html
I am using wxPython to create a splash screen. It actually works great, but now I want to put some loading feedback on the splashscreen. Does anyone know how I can put a dynamic label on top of the splashscreen? Here is the code I am using:
class myFrame(wxFrame):
wx.Frame.__init__(self, parent, -1, _("Radar"),
size=(800, 600), style=wx.DEFAULT_FRAME_STYLE)
class MySplash(wx.SplashScreen):
def __init__(self, parent=None):
aBitmap = wx.Image(name=VarFiles["Radar_Splash"]).ConvertToBitmap()
splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT
splashDuration = 12000 # ms
wx.SplashScreen.__init__(self, aBitmap, splashStyle, splashDuration, parent)
self.Bind(wx.EVT_CLOSE, self.CloseSplash)
wx.Yield()
def CloseSplash(self, evt):
self.Hide()
global frame
frame = myFrame(parent=None)
app.SetTopWindow(frame)
frame.Show(True)
evt.Skip()
class MyAwesomeApp(wx.App):
def OnInit(self):
MySplash = MySplash()
MySplash.Show()
return True
As the SplashScreen is essentially a window displaying a bitmap, you will need to modify the provided bitmap, doing the layouting yourself.
def _draw_bmp(bmp, txt_lst):
dc = wx.MemoryDC()
dc.SelectObject(bmp)
dc.Clear()
gc = wx.GraphicsContext.Create(dc)
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
gc.SetFont(font)
for i, line in enumerate(txt_lst):
dc.DrawText(line, 10, i * 20 + 15)
dc.SelectObject(wx.NullBitmap)
# ...
aBitmap = #...
_draw_bmp(aBitmap, ['splash', 'screen', 'text'])
The wx.GraphicsContext will be helpful to have antialiased text looking the same as in the underlying OS.
I'd like to program a table-like GUI. Do you know a powerful table widget (for any GUI), which has ready-made functionality like filtering, sorting, editing and alike (as seen in Excel)?
You can use wxGrid - here's some demo code - you need to manage/wire up all the events yourself on the underlying Table. Its a bit complicated to gove an explaination in words, here's some code (mostly based on the wx examples code):
import wx
from wx import EVT_MENU, EVT_CLOSE
import wx.grid as gridlib
from statusclient import JobDataTable, JobDataGrid
app = wx.App()
log = Logger(__name__)
class JobManager(wx.Frame):
def __init__(self, parent, title):
super(JobManager, self).__init__(parent, title=title)
panel = wx.Panel(self, -1)
self.client_id = job_server.register()
log.info('Registered with server as {}'.format(self.client_id))
self.jobs = job_server.get_all_jobs()
grid = self.create_grid(panel, self.jobs)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 1, wx.ALL|wx.EXPAND)
panel.SetSizer(sizer)
# Bind Close Event
EVT_CLOSE(self, self.exit)
self.Center()
self.Show()
def exit(self, event):
log.info('Unregistering {0} from server...'.format(self.client_id))
job_server.unregister(self.client_id)
job_server.close()
exit()
def create_grid(self, panel, data):
table = JobDataTable(jobs=data)
grid = JobDataGrid(panel)
grid.CreateGrid(len(data), len(data[0].keys()))
grid.SetTable(table)
grid.AutoSize()
grid.AutoSizeColumns(True)
return grid
def main():
frame = JobManager(None, 'Larskhill Job Manager')
app.MainLoop()
if __name__ == '__main__':
job_server = zerorpc.Client()
job_server.connect('tcp://0.0.0.0:4242')
main()
####
ui/client.py
####
import wx
import wx.grid as gridlib
EVEN_ROW_COLOUR = '#CCE6FF'
GRID_LINE_COLOUR = '#ccc'
COLUMNS = {0:('id', 'ID'), 1:('name', 'Name'), 2:('created_at', 'Created'), 3:('status', 'Current Status')}
log = Logger(__name__)
class JobDataTable(gridlib.PyGridTableBase):
"""
A custom wxGrid Table that expects a user supplied data source.
"""
def __init__(self, jobs=None):
gridlib.PyGridTableBase.__init__(self)
self.headerRows = 0
self.jobs = jobs
#-------------------------------------------------------------------------------
# Required methods for the wxPyGridTableBase interface
#-------------------------------------------------------------------------------
def GetNumberRows(self):
return len(self.jobs)
def GetNumberCols(self):
return len(COLUMNS.keys())
#---------------------------------------------------------------------------
# Get/Set values in the table. The Python version of these
# methods can handle any data-type, (as long as the Editor and
# Renderer understands the type too,) not just strings as in the
# C++ version. We load thises directly from the Jobs Data.
#---------------------------------------------------------------------------
def GetValue(self, row, col):
prop, label = COLUMNS.get(col)
#log.debug('Setting cell value')
return self.jobs[row][prop]
def SetValue(self, row, col, value):
pass
#---------------------------------------------------------------------------
# Some optional methods
# Called when the grid needs to display labels
#---------------------------------------------------------------------------
def GetColLabelValue(self, col):
prop, label = COLUMNS.get(col)
return label
#---------------------------------------------------------------------------
# Called to determine the kind of editor/renderer to use by
# default, doesn't necessarily have to be the same type used
# natively by the editor/renderer if they know how to convert.
#---------------------------------------------------------------------------
def GetTypeName(self, row, col):
return gridlib.GRID_VALUE_STRING
#---------------------------------------------------------------------------`
# Called to determine how the data can be fetched and stored by the
# editor and renderer. This allows you to enforce some type-safety
# in the grid.
#---------------------------------------------------------------------------
def CanGetValueAs(self, row, col, typeName):
pass
def CanSetValueAs(self, row, col, typeName):
pass
#---------------------------------------------------------------------------
# Style the table, stripy rows and also highlight changed rows.
#---------------------------------------------------------------------------
def GetAttr(self, row, col, prop):
attr = gridlib.GridCellAttr()
# Odd Even Rows
if row % 2 == 1:
bg_colour = EVEN_ROW_COLOUR
attr.SetBackgroundColour(bg_colour)
return attr
#-------------------------------------------------------------------------------
# Custom Job Grid
#-------------------------------------------------------------------------------
class JobDataGrid(gridlib.Grid):
def __init__(self, parent, size=wx.Size(1000, 500), data_table=None):
self.parent = parent
gridlib.Grid.__init__(self, self.parent, -1) # so grid references a weak reference to the parent
self.SetGridLineColour(GRID_LINE_COLOUR)
self.SetRowLabelSize(0)
self.SetColLabelSize(30)
self.table = JobDataTable()
Give me a shout if it needs clarification.
After looking at questions like this it doesn't make sense that my __init__(self, parrent, id) would be throwing a unbound error? help?
main.py
import wx
from customButton import customButton
from wxPython.wx import *
class MyFrame(wx.Frame):
def __init__(self, parent, ID, title):
wxFrame.__init__(self, parent, ID, title,
wxDefaultPosition, wxSize(400, 400))
# Non-important code here...
# This is the first declaration of the Button1
# This is also where the ERROR is thrown.
# Omitting this line causes the window to execute
# flawlessly.
self.Button1 = customButton.__init__(self, parent, -1)
# ... finishes in a basic wx.program style...
customButton.py
# I've included all of the code in the file
# because have no idea where the bug/error happens
import wx
from wxPython.wx import *
class Custom_Button(wx.PyControl):
# The BMP's
Over_bmp = None #wxEmptyBitmap(1,1,1) # When the mouse is over
Norm_bmp = None #wxEmptyBitmap(1,1,1) # The normal BMP
Push_bmp = None #wxEmptyBitmap(1,1,1) # The down BMP
def __init__(self, parent, id, **kwargs):
wx.PyControl.__init__(self,parent, id, **kwargs)
# Set the BMP's to the ones given in the constructor
#self.Over_bmp = wx.Bitmap(wx.Image(MOUSE_OVER_BMP, wx.BITMAP_TYPE_ANY).ConvertToBitmap())
#self.Norm_bmp = wx.Bitmap(wx.Image(NORM_BMP, wx.BITMAP_TYPE_ANY).ConvertToBitmap())
#self.Push_bmp = wx.Bitmap(wx.Image(PUSH_BMP, wx.BITMAP_TYPE_ANY).ConvertToBitmap())
#self.Pos_bmp = self.pos
self.Bind(wx.EVT_LEFT_DOWN, self._onMouseDown)
self.Bind(wx.EVT_LEFT_UP, self._onMouseUp)
self.Bind(wx.EVT_LEAVE_WINDOW, self._onMouseLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self._onMouseEnter)
self.Bind(wx.EVT_ERASE_BACKGROUND,self._onEraseBackground)
self.Bind(wx.EVT_PAINT,self._onPaint)
self._mouseIn = self._mouseDown = False
def _onMouseEnter(self, event):
self._mouseIn = True
def _onMouseLeave(self, event):
self._mouseIn = False
def _onMouseDown(self, event):
self._mouseDown = True
def _onMouseUp(self, event):
self._mouseDown = False
self.sendButtonEvent()
def sendButtonEvent(self):
event = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
event.SetInt(0)
event.SetEventObject(self)
self.GetEventHandler().ProcessEvent(event)
def _onEraseBackground(self,event):
# reduce flicker
pass
def _onPaint(self, event):
dc = wx.BufferedPaintDC(self)
dc.SetFont(self.GetFont())
dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
dc.Clear()
dc.DrawBitmap(self.Norm_bmp)
# draw whatever you want to draw
# draw glossy bitmaps e.g. dc.DrawBitmap
if self._mouseIn: # If the Mouse is over the button
dc.DrawBitmap(self, self.Mouse_over_bmp, self.Pos_bmp, useMask=False)
if self._mouseDown: # If the Mouse clicks the button
dc.DrawBitmap(self, self.Push_bmp, self.Pos_bmp, useMask=False)
You don't create an object like this:
self.Button1 = customButton.__init__(self, parent, -1)
you do it like this:
self.Button1 = customButton(parent, -1)
__init__ is an implicitly invoked method during object creation.
Don't call __init__() explicitly unless you know you need to.
self.Button1 = customButton(parent, -1)