I've created a box-select feature for tk.Text. The widget gets the font height and concocts an .xbm image from it. The .xbm is used as a faux-caret, via image_create, for all selected lines except the line the real caret is on.
How do I make the faux-caret image instance(s) blink in time with the real caret?
or
What is another direction I can go to get these results?
My solution was to get rid of the real caret visibly, draw a faux caret on every line, and then call a function that keeps swapping out an 'on' faux-caret with an 'off' faux-caret. Below are some relevant code snippets. Note: I actually have 3 faux-carets because I want the caret that is on the true active line to be a little darker.
#FAUX-CARET
#concoct, load, assign, and delete xbm for faux-caret
def __loadcarets(self) -> None:
#store insert on/off times for faux-caret `.after` calls
self.__instime = (self['insertofftime'], self['insertontime'])
fh = self.__fh #font height from 'linespace'
#make a temp xbm file
with tempfile.NamedTemporaryFile(mode='w+b', suffix='.xbm', delete=False) as f:
#create prettyprint xbm data
xbmdata = ',\n\t'.join(','.join('0xFF' for _ in range(min(8, fh-(8*i)))) for i in range(math.ceil(fh/8)))
#write xbm
f.write((f"#define image_width {INSWIDTH}\n#define image_height {fh}\n"
"static unsigned char image_bits[] = {\n\t"
f'{xbmdata}}};').encode())
#load xbm files for faux-caret ~ they have to be in this order
#I gave this a placeholder name because you should never have to access this directly
#__fauxcaret does everything
self.__ = (tk.BitmapImage(file=f.name, foreground='#999999'), #shadow caret
tk.BitmapImage(file=f.name, foreground=self['background']), #off caret
tk.BitmapImage(file=f.name, foreground=self['insertbackground'])) #main caret
#delete file
os.unlink(f.name)
#faux-caret create or config
def __fauxcaret(self, index:str, on:bool=True, main:bool=False, cfg:bool=False) -> None:
(self.image_create, self.image_configure)[cfg](index, image=self.__[(main<<on)|(on^1)])
#blink the faux-caret(s)
def __blink(self, on:bool=True):
#nothing to do
if not self.__boxselect: return
#so we only do this once per first blink
if not self.__blinksrt:
#sort image indexes
self.__blinksrt = sorted((self.index(n) for n in self.image_names()), key=float)
if idx:=self.__blinksrt:
#flip `on`
on = not on
#consider direction in forward perspective
fw = not self.__lbounds.rv
#reconfigure all carets
for i in idx: self.__fauxcaret(i, on=on, cfg=True)
#reconfigure "active line" caret, if off it will assign off again
self.__fauxcaret(idx[-fw], on=on, main=True, cfg=True)
#schedule next call
self.__blinkid = self.after(self.__instime[on], self.__blink, on)
return
raise ValueError('__blink: Nothing to sort!')
#reset blink
def __blinkreset(self) -> None:
#cancel after
self.after_cancel(self.__blinkid)
#reset blinksort
self.__blinksrt = None
Related
I want to make a program that show me the divisors of the number introduced in a text box using python tkinter GUI and store the results into a plain text file.
I don't how to get the value from the text box. I understood that is something linked with get() , I read something but I still don't get it.
Here is the code:
from tkinter import *
def create_file():
file_object = open("C:/Users/valis/Desktop/Divisors.txt","w+")
def evaluate():
show_after= Label(text= "Check your Desktop !")
show_after.pack(pady=2, anchor=SW)
create_file()
#Windows size and Title
window = Tk()
window.title("Show Divisors")
window.geometry("300x100")
message = Label(window, text="Enter a number : ")
message.pack(pady=2, anchor=NW)
textbox_input = Text(window,height=1, width=11)
textbox_input.pack(padx= 2,pady= 2, anchor=NW)
window.mainloop()
The code isn't complete, so what should I do ?
As you said, you will use the get() function but with some additional attributes.
If we have a text box textbox_input, then you can return its input using this line:
test_input = textbox_input.get("1.0",END)
The first part, "1.0" means that the input should be read from line one, character zero (ie: the very first character). END is an imported constant which is set to the string "end". The END part means to read until the end of the text box is reached.
Reference: This answer.
I have a data project that produces a number of plots with the same name only in different directories. Looking at these plots helps to see if anything funky is going on with the data, but it would be a pain try to open them all one by one or to copy them all to one directory, rename them, and then open up my standard image viewer.
Knowing some wxPython GUI programming I decided to whip up an app that would take in a list of directories to these images and I can slide show them with some arrow buttons. I can then add to this list as more plots come in.
I got the app running properly, but now when I am loading in around 23 ~3 MB images my program memory usage reaches near 10 GB.
The code is below, but in short I load them up all as wxPython Images, before displaying, and this is where the memory climbs. Switching between the images via stacks doesn't seem to make the memory climb further. I think it might be me holding onto no longer needed resources, however the usage seems disproportionate to the size and number of images.
Is someone able to point out a flaw in my method?
```
class Slide(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Slide Show")
self.display = wx.GetDisplaySize()
self.image_list = None
self.current = None # holds currently displayed bitmap
# stacks that hold the bitmaps
self.future = []
self.past = []
self.left = wx.BitmapButton(self, id=101, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_GO_BACK))
self.left.Enable(False)
self.right = wx.BitmapButton(self, id=102, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_GO_FORWARD))
self.right.Enable(False)
self.open = wx.BitmapButton(self, id=103, size=(100,-1), bitmap=wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN))
# Testing code
#png = wx.Image("set001_fit_001_1.png", wx.BITMAP_TYPE_ANY).ConvertToBitmap()
#image = wx.ImageFromBitmap(png)
#image = image.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
#png = wx.BitmapFromImage(image)
# make empty image slot
self.img = wx.StaticBitmap(self, -1, size=(self.display[0]/1.4, self.display[1]/1.4))
## Main sizers
self.vertSizer = wx.BoxSizer(wx.VERTICAL)
# sub sizers
self.buttonSizer = wx.BoxSizer(wx.HORIZONTAL)
# place buttons together
self.buttonSizer.Add(self.left, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.buttonSizer, 15)
self.buttonSizer.Add(self.right, flag=wx.ALIGN_CENTER)
# place arrows and open together
self.vertSizer.Add(self.img, flag=wx.ALIGN_CENTER)
self.vertSizer.Add(self.open, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.vertSizer, 15)
self.vertSizer.Add(self.buttonSizer, flag=wx.ALIGN_CENTER)
als.AddLinearSpacer(self.vertSizer, 15)
self.Bind(wx.EVT_BUTTON, self.onOpen, id=103)
self.Bind(wx.EVT_BUTTON, self.onLeft, id=101)
self.Bind(wx.EVT_BUTTON, self.onRight, id=102)
self.SetSizer(self.vertSizer)
self.vertSizer.Fit(self)
def onOpen(self, evt):
print("Opening")
openFileDialog = wx.FileDialog(self, "Open Image List", "", "", "List (*.ls)|*.ls", wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return
fileName = str(openFileDialog.GetPath())
print(fileName)
# put image list in a python list
image_list = []
f = open(fileName, 'r')
for line in f:
image_list.append(line.rstrip())
f.close()
print(image_list)
# convert every image to wx Image
png_list = []
for image in image_list:
png = wx.Image(image, wx.BITMAP_TYPE_ANY)
png_list.append(png)
# store pngs
print(image_list[::-1])
png_list = png_list[::-1]
for png in png_list[:-1]:
self.future.append(png)
# display first png
self.current = png_list[-1] # put into current buffer
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
self.right.Enable(True)
def onLeft(self, evt):
# put current image into future stack and grab a new current image from past stack
self.future.append(self.current)
self.current = self.past.pop()
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
if len(self.past) <= 0:
self.left.Enable(False)
self.right.Enable(True)
def onRight(self, evt):
# put current image into past stack and load in a new image from future stack
self.past.append(self.current)
self.current = self.future.pop()
png = self.current.Scale(self.display[0]/1.4, self.display[1]/1.4, wx.IMAGE_QUALITY_HIGH)
png = wx.BitmapFromImage(png)
self.img.SetBitmap(png)
self.Refresh()
if len(self.future) <= 0:
self.right.Enable(False)
self.left.Enable(True)
```
Loading them as needed would probably be the best way to reduce memory usage. Or perhaps keeping the current, next and previous images in memory to help speed up switching between views. Or maybe use a caching implementation to keep the N most recently viewed images (plus the next in the sequence) in memory. Then you can fiddle with the size of N to see what works well and is a reasonable compromise between memory and speed.
Another possible improvement would be to not scale the image each time you need to display it, just scale it once when it is read and keep that one in memory (or the cache). You may also want to experiment with pre-converting them to wx.Bitmaps too, as image to bitmap conversions is not a trivial operation.
I'm having some trouble rescaling video output of GStreamer to the dimension of the window the video is displayed in (retaining aspect ratio of the video). The problem is that I first need to preroll the video to be able to determine its dimensions by retrieving the negotiated caps, and then calculate the dimensions it needs to be displayed in to fit the window. Once I have prerolled the video and got the dimension caps, I cannot change the video's dimension anymore. Setting the new caps still results in the video being output in its original size. What must I do to solve this?
Just to be complete. In the current implementation I cannot render to an OpenGL texture which would have easily solved this problem because you could simply render output to the texture and scale it to fit the screen. I have to draw the output on a pygame surface, which needs to have the correct dimensions. pygame does offer functionality to scale its surfaces, but I think such an implementation (as I have now) is slower than retrieving the frames in their correct size directly from GStreamer (am I right?)
This is my code for loading and displaying the video (I omitted the main loop stuff):
def calcScaledRes(self, screen_res, image_res):
"""Calculate image size so it fits the screen
Args
screen_res (tuple) - Display window size/Resolution
image_res (tuple) - Image width and height
Returns
tuple - width and height of image scaled to window/screen
"""
rs = screen_res[0]/float(screen_res[1])
ri = image_res[0]/float(image_res[1])
if rs > ri:
return (int(image_res[0] * screen_res[1]/image_res[1]), screen_res[1])
else:
return (screen_res[0], int(image_res[1]*screen_res[0]/image_res[0]))
def load(self, vfile):
"""
Loads a videofile and makes it ready for playback
Arguments:
vfile -- the uri to the file to be played
"""
# Info required for color space conversion (YUV->RGB)
# masks are necessary for correct display on unix systems
_VIDEO_CAPS = ','.join([
'video/x-raw-rgb',
'red_mask=(int)0xff0000',
'green_mask=(int)0x00ff00',
'blue_mask=(int)0x0000ff'
])
self.caps = gst.Caps(_VIDEO_CAPS)
# Create videoplayer and load URI
self.player = gst.element_factory_make("playbin2", "player")
self.player.set_property("uri", vfile)
# Enable deinterlacing of video if necessary
self.player.props.flags |= (1 << 9)
# Reroute frame output to Python
self._videosink = gst.element_factory_make('appsink', 'videosink')
self._videosink.set_property('caps', self.caps)
self._videosink.set_property('async', True)
self._videosink.set_property('drop', True)
self._videosink.set_property('emit-signals', True)
self._videosink.connect('new-buffer', self.__handle_videoframe)
self.player.set_property('video-sink', self._videosink)
# Preroll movie to get dimension data
self.player.set_state(gst.STATE_PAUSED)
# If movie is loaded correctly, info about the clip should be available
if self.player.get_state(gst.CLOCK_TIME_NONE)[0] == gst.STATE_CHANGE_SUCCESS:
pads = self._videosink.pads()
for pad in pads:
caps = pad.get_negotiated_caps()[0]
self.vidsize = caps['width'], caps['height']
else:
raise exceptions.runtime_error("Failed to retrieve video size")
# Calculate size of video when fit to screen
self.scaledVideoSize = self.calcScaledRes((self.screen_width,self.screen_height), self.vidsize)
# Calculate the top left corner of the video (to later center it vertically on screen)
self.vidPos = ((self.screen_width - self.scaledVideoSize [0]) / 2, (self.screen_height - self.scaledVideoSize [1]) / 2)
# Add width and height info to video caps and reload caps
_VIDEO_CAPS += ", width={0}, heigh={1}".format(self.scaledVideoSize[0], self.scaledVideoSize[1])
self.caps = gst.Caps(_VIDEO_CAPS)
self._videosink.set_property('caps', self.caps) #??? not working, video still displayed in original size
def __handle_videoframe(self, appsink):
"""
Callback method for handling a video frame
Arguments:
appsink -- the sink to which gst supplies the frame (not used)
"""
buffer = self._videosink.emit('pull-buffer')
img = pygame.image.frombuffer(buffer.data, self.vidsize, "RGB")
# Upscale image to new surfuace if presented fullscreen
# Create the surface if it doesn't exist yet and keep rendering to this surface
# for future frames (should be faster)
if not hasattr(self,"destSurf"):
self.destSurf = pygame.transform.scale(img, self.destsize)
else:
pygame.transform.scale(img, self.destsize, self.destSurf)
self.screen.blit(self.destSurf, self.vidPos)
# Swap the buffers
pygame.display.flip()
# Increase frame counter
self.frameNo += 1
I'm pretty sure that your issue was (has it is very long time since you asked this question) that you never hooked up the bus to watch for messages that were emitted.
The code for this is usually something like this:
def some_function(self):
#code defining Gplayer (the pipeline)
#
# here
Gplayer.set_property('flags', self.GST_VIDEO|self.GST_AUDIO|self.GST_TEXT|self.GST_SOFT_VOLUME|self.GST_DEINTERLACE)
# more code
#
# finally
# Create the bus to listen for messages
bus = Gplayer.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect('message', self.OnBusMessage)
bus.connect('sync-message::element', self.OnSyncMessage)
# Listen for gstreamer bus messages
def OnBusMessage(self, bus, message):
t = message.type
if t == Gst.MessageType.ERROR:
pass
elif t == Gst.MessageType.EOS:
print ("End of Audio")
return True
def OnSyncMessage(self, bus, msg):
if msg.get_structure() is None:
return True
if message_name == 'prepare-window-handle':
imagesink = msg.src
imagesink.set_property('force-aspect-ratio', True)
imagesink.set_window_handle(self.panel1.GetHandle())
The key bit for your issue is setting up a call back for the sync-message and in that call-back, setting the property force-aspect-ratio to True.
This property ensures that the video fits the window it is being displayed in at all times.
Note the self.panel1.GetHandle() function names the panel in which you are displaying the video.
I appreciate that you will have moved on but hopefully this will help someone else trawling through the archives.
I'm trying to color some specific part of the text, i have tried to say:
if word.strip().startswith(":"):
self.setAttributesForRange(NSColor.greenColor(), None, highlightOffset, len(word))
When someone types the sign : it gets colored green. That is good, but it keeps coloring the word after it like this:
:Hello Hello :Hello <---- this all gets colored green, but I want something like:
:Hello Hello :Hello <---- where everything get colored except the middle "hello" because it doesn't start with the sign : , please help me out
from Foundation import *
from AppKit import *
import objc
class PyObjC_HighlightAppDelegate(NSObject):
# The connection to our NSTextView in the UI
highlightedText = objc.IBOutlet()
# Default font size to use when highlighting
fontSize = 12
def applicationDidFinishLaunching_(self, sender):
NSLog("Application did finish launching.")
def textDidChange_(self, notification):
"""
Delegate method called by the NSTextView whenever the contents of the
text view have changed. This is called after the text has changed and
been committed to the view. See the Cocoa reference documents:
http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSText_Class/Reference/Reference.html
http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSTextView_Class/Reference/Reference.html
Specifically the sections on Delegate Methods for information on additional
delegate methods relating to text control is NSTextView objects.
"""
# Retrieve the current contents of the document and start highlighting
content = self.highlightedText.string()
self.highlightText(content)
def setAttributesForRange(self, color, font, rangeStart, rangeLength):
"""
Set the visual attributes for a range of characters in the NSTextView. If
values for the color and font are None, defaults will be used.
The rangeStart is an index into the contents of the NSTextView, and
rangeLength is used in combination with this index to create an NSRange
structure, which is passed to the NSTextView methods for setting
text attributes. If either of these values are None, defaults will
be provided.
The "font" parameter is used as an key for the "fontMap", which contains
the associated NSFont objects for each font style.
"""
fontMap = {
"normal" : NSFont.systemFontOfSize_(self.fontSize),
"bold" : NSFont.boldSystemFontOfSize_(self.fontSize)
}
# Setup sane defaults for the color, font and range if no values
# are provided
if color is None:
color = NSColor.blackColor()
if font is None:
font = "normal"
if font not in fontMap:
font = "normal"
displayFont = fontMap[font]
if rangeStart is None:
rangeStart = 0
if rangeLength is None:
rangeLength = len(self.highlightedText.string()) - rangeStart
# Set the attributes for the specified character range
range = NSRange(rangeStart, rangeLength)
self.highlightedText.setTextColor_range_(color, range)
self.highlightedText.setFont_range_(displayFont, range)
def highlightText(self, content):
"""
Apply our customized highlighting to the provided content. It is assumed that
this content was extracted from the NSTextView.
"""
# Calling the setAttributesForRange with no values creates
# a default that "resets" the formatting on all of the content
self.setAttributesForRange(None, None, None, None)
# We'll highlight the content by breaking it down into lines, and
# processing each line one by one. By storing how many characters
# have been processed we can maintain an "offset" into the overall
# content that we use to specify the range of text that is currently
# being highlighted.
contentLines = content.split("\n")
highlightOffset = 0
for line in contentLines:
if line.strip().startswith("#"):
# Comment - we want to highlight the whole comment line
self.setAttributesForRange(NSColor.greenColor(), None, highlightOffset, len(line))
elif line.find(":") > -1:
# Tag - we only want to highlight the tag, not the colon or the remainder of the line
startOfLine = line[0: line.find(":")]
yamlTag = startOfLine.strip("\t ")
yamlTagStart = line.find(yamlTag)
self.setAttributesForRange(NSColor.blueColor(), "bold", highlightOffset + yamlTagStart, len(yamlTag))
elif line.strip().startswith("-"):
# List item - we only want to highlight the dash
listIndex = line.find("-")
self.setAttributesForRange(NSColor.redColor(), None, highlightOffset + listIndex, 1)
# Add the processed line to our offset, as well as the newline that terminated the line
highlightOffset += len(line) + 1
It all depends on what word is.
In [6]: word = ':Hello Hello :Hello'
In [7]: word.strip().startswith(':')
Out[7]: True
In [8]: len(word)
Out[8]: 19
Compare:
In [1]: line = ':Hello Hello :Hello'.split()
In [2]: line
Out[2]: [':Hello', 'Hello', ':Hello']
In [3]: for word in line:
print word.strip().startswith(':')
print len(word)
...:
True
6
False
5
True
6
Notice the difference in len(word), which I suspect is causing your problem.
Well, while I don't consider myself an experienced programmer, especially when it comes to multimedia applications, I had to do this image viewer sort of a program that displays images fullscreen, and the images change when the users press <- or -> arrows.
The general idea was to make a listbox where all the images contained in a certain folder (imgs) are shown, and when someone does double click or hits RETURN a new, fullscreen frame is generated containing, at first, the image selected in the listbox but after the user hits the arrows the images change in conecutive order, just like in a regular image viewer.
At the beginning, when the first 15 - 20 images are generated, everything goes well, after that, although the program still works an intermediary, very unpleasant and highly unwanted, effect appears between image generations, where basically some images that got generated previously are displayed quickly on the screen and after this the right, consecutive image appears. On the first apparitions the effect is barelly noticeble, but after a while it takes longer and longer between generations.
Here's the code that runs when someone does double click on a listbox entry:
def lbclick(self, eve):
frm = wx.Frame(None, -1, '')
frm.ShowFullScreen(True)
self.sel = self.lstb.GetSelection() # getting the selection from the listbox
def pressk(eve):
keys = eve.GetKeyCode()
if keys == wx.WXK_LEFT:
self.sel = self.sel - 1
if self.sel < 0:
self.sel = len(self.chk) - 1
imgs() # invocking the function made for displaying fullscreen images when left arrow key is pressed
elif keys == wx.WXK_RIGHT:
self.sel = self.sel + 1
if self.sel > len(self.chk) - 1:
self.sel = 0
imgs() # doing the same for the right arrow
elif keys == wx.WXK_ESCAPE:
frm.Destroy()
eve.Skip()
frm.Bind(wx.EVT_CHAR_HOOK, pressk)
def imgs(): # building the function
imgsl = self.chk[self.sel]
itm = wx.Image(str('imgs/{0}'.format(imgsl)), wx.BITMAP_TYPE_JPEG).ConvertToBitmap() # obtaining the name of the image stored in the list self.chk
mar = itm.Size # Because not all images are landscaped I had to figure a method to rescale them after height dimension, which is common to all images
frsz = frm.GetSize()
marx = float(mar[0])
mary = float(mar[1])
val = frsz[1] / mary
vsize = int(mary * val)
hsize = int(marx * val)
panl = wx.Panel(frm, -1, size = (hsize, vsize), pos = (frsz[0] / 2 - hsize / 2, 0)) # making a panel container
panl.SetBackgroundColour('#000000')
imag = wx.Image(str('imgs/{0}'.format(imgsl)), wx.BITMAP_TYPE_JPEG).Scale(hsize, vsize, quality = wx.IMAGE_QUALITY_NORMAL).ConvertToBitmap()
def destr(eve): # unprofessionaly trying to destroy the panel container when a new image is generated hopeing the unvanted effect, with previous generated images will disappear. But it doesn't.
keycd = eve.GetKeyCode()
if keycd == wx.WXK_LEFT or keycd == wx.WXK_RIGHT:
try:
panl.Destroy()
except:
pass
eve.Skip()
panl.Bind(wx.EVT_CHAR_HOOK, destr) # the end of my futile tries
if vsize > hsize: # if the image is portrait instead of landscaped I have to put a black image as a container, otherwise in the background the previous image will remain, even if I use Refresh() on the container (the black bitmap named fundal)
intermed = wx.Image('./res/null.jpg', wx.BITMAP_TYPE_JPEG).Scale(frsz[0], frsz[1]).ConvertToBitmap()
fundal = wx.StaticBitmap(frm, 101, intermed)
stimag = wx.StaticBitmap(fundal, -1, imag, size = (hsize, vsize), pos = (frsz[0] / 2 - hsize / 2, 0))
fundal.Refresh()
stimag.SetToolTip(wx.ToolTip('Esc = exits fullscreen\n<- -> arrows = quick navigation'))
def destr(eve): # the same lame attempt to destroy the container in the portarit images situation
keycd = eve.GetKeyCode()
if keycd == wx.WXK_LEFT or keycd == wx.WXK_RIGHT:
try:
fundal.Destroy()
except:
pass
eve.Skip()
frm.Bind(wx.EVT_CHAR_HOOK, destr)
else: # the case when the images are landscape
stimag = wx.StaticBitmap(panl, -1, imag)
stimag.Refresh()
stimag.SetToolTip(wx.ToolTip('Esc = exits fullscreen\n<- -> arrows = quick navigation'))
imgs() # invocking the function imgs for the situation when someone does double click
frm.Center()
frm.Show(True)
Thanks for any advice in advance.
Added later:
The catch is I'm trying to do an autorun presentation for a DVD with lots of images on it. Anyway it's not necessarely to make the above piece of code work properly if there are any other options. I've already tried to use windows image viewer, but strangely enough it doesn't recognizes relative paths and when I do this
path = os.getcwd() # getting the path of the current working directory
sel = listbox.GetSelection() # geting the value of the current selection from the list box
imgname = memlist[sel] # retrieving the name of the images stored in a list, using the listbox selection, that uses as choices the same list
os.popen(str('rundll32.exe C:\WINDOWS\System32\shimgvw.dll,ImageView_Fullscreen {0}/imgsdir/{1}'.format(path, imgname))) # oepning the images up with bloody windows image viewer
it opens the images only when my program is on the hard disk, if it's on a CD / image drive it doesn't do anything.
There's an image viewer included with the wxPython demo package. I also wrote a really simple one here. Either one should help you in your journey. I suspect that you're not reusing your panels/frames and instead you're seeing old ones that were never properly destroyed.