问题
I am writing a GUI to display a few images (book covers from amazon) from a defined list of URLs, I have been able to load them in a panel successfully, but in spite of using threads, the GUI seems to wait till all the loop is over and then all the images show up at once, how can I achieve the GUI to display each images as they are fetched run time..the GUI is basically frozen till the images are fetched...Thanks!
the question again is to ensure I get to display each image on the GUI as they are pulled and not all at once...
import wx
import os
import sys
import urllib2
import cStringIO
import threading
import time
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.makeButtons()
def makeButtons(self):
def _update_data(data):
time.sleep(2)
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage( wx.ImageFromStream( stream ) )
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetBitmapMargins((4,4))
button.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD, False))
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.Show(True)
self.panel.Layout()
def f():
f = urllib2.urlopen(url)
data = f.read()
wx.CallAfter(_update_data, data)
for url in urls:
threading.Thread(target=f).start()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
回答1:
It would also be possible to spin off all threads at once, as has been tried in the code sample in the question. The problem in the original code was that the GUI was blocking due to the time delay in processing the request for the bitmaps.
Bear in mind that in the sample below the order of the bitmaps will depend on the order the threads are finishing (which is random in this case).
import wx
import urllib2
import cStringIO
import threading
import time
from random import random
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def url2bmp(url, callback):
f = urllib2.urlopen(url)
data = f.read()
# to simulate random read delay
time.sleep(2 * random())
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
wx.CallAfter(callback, bmp)
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
self.makeButtons()
def makeButtons(self):
for url in urls:
threading.Thread(target=url2bmp, args=(url, self.update_ui)).start()
def update_ui(self, bmp):
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
EDIT: Rewrote the example to generate the empty buttons first, then apply the bitmap as soon as they arrive. This is uglier now and it would be time to refactor to have a more safe mapping button to url
than (mis)using the label.
import wx
import urllib2
import cStringIO
import threading
import time
from random import random
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def url2bmp(url, callback):
f = urllib2.urlopen(url)
data = f.read()
# to simulate random read delay
time.sleep(2 * random())
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
wx.CallAfter(callback, url, bmp)
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
self.makeButtons()
def makeButtons(self):
for url in urls:
self.update_ui(url)
threading.Thread(target=url2bmp, args=(url, self.update_ui)).start()
def update_ui(self, url, bmp=None):
if bmp is None:
# create button, but not bitmap
button = wx.Button(self.panel, -1, url, style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
self.wrapSizer.Add(button, 1, wx.EXPAND)
else:
children = self.wrapSizer.GetChildren()
# http://www.blog.pythonlibrary.org/2012/08/24/wxpython-how-to-get-children-widgets-from-a-sizer/
for widget in children:
button = widget.GetWindow()
if isinstance(button, wx.Button):
if button.GetLabel() == url:
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetLabel('')
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
回答2:
Have allowed myself to rewrite. Important is to get out of the __init__
method as soon as possible (calling Show
before starting the thread) and letting the thread working asynchronuosly. You also started all your threads at once, while this example uses one thread to get one bitmap after the other.
import wx
import os
import sys
import urllib2
import cStringIO
import threading
import time
urls = ['https://images-na.ssl-images-amazon.com/images/I/51-u3J3mtTL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51cRqX8DTgL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/515iBchIIzL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/511MaP7GeJL._AC_US100_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51jizRmRYYL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/31Pw7voGDFL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51g30m1xpPL._AC_US160_.jpg',
'https://images-na.ssl-images-amazon.com/images/I/51qx+6aQUnL._AC_US160_.jpg']
def gen_urls():
"""Gives back one bitmap after another."""
for url in urls:
f = urllib2.urlopen(url)
data = f.read()
time.sleep(2)
stream = cStringIO.StringIO(data)
bmp = wx.BitmapFromImage( wx.ImageFromStream( stream ) )
yield bmp
class Example(wx.Frame):
def __init__(self, *args, **kwargs):
super(Example, self).__init__(*args, **kwargs)
self.InitUI()
self.Ctrls()
self.Show(True)
threading.Thread(target=self.makeButtons).start()
def makeButtons(self):
for bmp in gen_urls():
wx.CallAfter(self.update_ui, bmp)
def update_ui(self, bmp):
button = wx.Button(self.panel, -1, "Book cover", style=wx.ALIGN_CENTER, size=wx.Size(100,100))
button.SetToolTipString("wx.Button can how have an icon on the left, right,\n"
"above or below the label.")
button.SetBitmap(bmp,
wx.LEFT # Left is the default, the image can be on the other sides too
#wx.RIGHT
#wx.TOP
#wx.BOTTOM
)
button.SetBitmapMargins((4,4))
button.SetFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD, False))
self.wrapSizer.Add(button, 1, wx.EXPAND)
self.panel.Layout()
def InitUI(self):
self.SetSize((800, 400))
self.SetTitle('Dynamically Flow Buttons to Next Row on Window-Resize')
self.Centre()
def Sizers(self):
self.wrapSizer = wx.WrapSizer()
self.panel.SetSizer(self.wrapSizer)
def Ctrls(self):
self.panel = wx.Panel(parent=self,pos=wx.Point(0,0), size=wx.Size(750,550), style=wx.TAB_TRAVERSAL)
self.Sizers()
def main():
ex = wx.App()
Example(None)
ex.MainLoop()
if __name__ == '__main__':
main()
来源:https://stackoverflow.com/questions/41339294/wxpython-threading-display-images-as-they-are-loaded