wxpython - resize image without flicker? - python

I'm new to wxPython and GUI in general. Right now the application just displays a toolbar, statusbar, and the following panel. The panel contains a boxSizer with a staticBitmap in it. I'm trying to have an image resize itself to fit its container whenever the window is resized, but I'm running into a lot of flickering.
Summary
resizeImage() is called when the window is resized (EVT_SIZE fires)
resizeImage() resizes the panel to fit the new dimensions and then scales the image with scaleImage() and it is placed into the staticBitmap
resizeImage() basically grabs the image object, resizes it, sets it to a bitmap, and then sets it to the staticbitmap to be displayed.
Code
class Canvas(wx.Panel):
"""Panel used to display selected images"""
#---------------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
# Globals
self.image = wx.EmptyImage(1,1)
self.control = wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(self.image))
self.background = wx.BLACK
self.padding = 5
self.imageList = []
self.current = 0
self.total = 0
# Register Events
Publisher().subscribe(self.onLoadDirectory, ("load directory"))
Publisher().subscribe(self.resizeImage, ("resize window"))
# Set Layout
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.mainSizer.Add(self.control, 1, wx.ALL|wx.CENTER|wx.EXPAND,
self.padding)
self.SetSizer(self.mainSizer)
self.SetBackgroundColour(self.background)
#---------------------------------------------------------------------------
def scaleImage(self, image, maxWidth, maxHeight):
"""asd"""
width = image.GetWidth()
height = image.GetHeight()
ratio = min( maxWidth / width, maxHeight/ height );
image = image.Scale(ratio*width, ratio*height, wx.IMAGE_QUALITY_HIGH)
result = wx.BitmapFromImage(image)
return result
#---------------------------------------------------------------------------
def loadImage(self, image):
"""Load image"""
self.image = wx.Image(image, wx.BITMAP_TYPE_ANY)
bmp = wx.BitmapFromImage(self.image)
w, h = self.mainSizer.GetSize()
w = w - self.padding*2
h = h - self.padding*2
bmp = self.scaleImage(self.image, w, h)
self.control.SetBitmap(bmp)
#---------------------------------------------------------------------------
def getImageIndex(self, path):
"""Retrieve index of image from imagePaths"""
i = 0
for image in self.imagePaths:
if image == path:
return i
i += 1
return -1
#---------------------------------------------------------------------------
def resizeImage(self, event):
self.SetSize(event.data)
if self.total:
w = event.data[0] - self.padding*2
h = event.data[1] - self.padding*2
bmp = self.scaleImage(self.image, w, h)
self.control.SetBitmap(bmp)
#---------------------------------------------------------------------------
def onLoadDirectory(self, event):
"""Load the image and compile a list of image files from directory"""
self.folderPath = os.path.dirname(event.data)
self.imagePaths = glob.glob(self.folderPath + "\\*.jpg")
self.total = len(self.imagePaths)
self.current = self.getImageIndex(event.data)
self.SetSize(self.GetSize())
self.loadImage(self.imagePaths[self.current])

Try drawing on a double buffered DC instead of using a StaticBitmap.

In your resizeImage method, it might help to add a Freeze and a Thaw, like this:
def resizeImage(self, event):
self.SetSize(event.data)
if self.total:
w = event.data[0] - self.padding*2
h = event.data[1] - self.padding*2
self.Freeze()
bmp = self.scaleImage(self.image, w, h)
self.control.SetBitmap(bmp)
self.Thaw()

Related

How to add smaller panel on top of existing panel inside frame? - WXPYTHON

Below I have an example of my code, you can drag and drop an image from your desktop into the window and it will change the image in the app. I want to only drag/drop images into the image, not the border around the image in the window.
In another words, you can drag/drop images anywhere in the window, but I only want you to be able to drag/drop images on top of the image.
import wx
from wx import *
import wx.lib.statbmp as SB
from PIL import Image
from pubsub import pub
import wx.lib.inspection #inspection tool
#=======================================================================#
# DRAG/DROP
#=======================================================================#
PhotoMaxSize = 485
class DropTarget(wx.FileDropTarget):
def __init__(self, widget):
wx.FileDropTarget.__init__(self)
self.widget = widget
def OnDropFiles(self, x, y, filenames):
pub.sendMessage('dnd', filepath=filenames[0])
return True
#=======================================================================#
# FRAME
#=======================================================================#
class PhotoCtrl(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Learning GUI', size=(800,500), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
self.SetBackgroundColour('#1a1a1a')
self.panel = MainPanel(self)
self.main_sizer = wx.BoxSizer()
self.main_sizer.Add(self.panel, 0, wx.EXPAND, 10)
self.Show()
#=======================================================================#
# DRAG/DROP IMAGE PANEL
#=======================================================================#
class MainPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.parent = parent
bg3 = wx.Image('bg3.jpg', wx.BITMAP_TYPE_ANY)
img = wx.Image(bg3)
self.image_ctrl = SB.GenStaticBitmap(
self, wx.ID_ANY, wx.Bitmap(img))
file_drop_target = DropTarget(self)
self.SetDropTarget(file_drop_target)
pub.subscribe(self.update_image_on_dnd, 'dnd')
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(5)
sizer.Add(self.image_ctrl, 0, wx.ALIGN_CENTER, 10)
sizer.AddStretchSpacer(1)
self.SetSizer(sizer)
def update_image_on_dnd(self, filepath):
self.on_view(filepath=filepath)
def on_view(self, filepath):
img = wx.Image(filepath, wx.BITMAP_TYPE_ANY)
W = img.GetWidth()
H = img.GetHeight()
if W > H:
new_w = PhotoMaxSize
new_h = PhotoMaxSize * H / W
else:
new_h = PhotoMaxSize
new_w = PhotoMaxSize * W / H
img = img.Scale(new_w, new_h)
self.image_ctrl.SetBitmap(wx.Bitmap(img))
#self.Fit() #removing this makes window size not change when dropping pic in
self.parent.Fit()
#=======================================================================#
# END
#=======================================================================#
if __name__ == '__main__':
app = wx.App()
frame = PhotoCtrl()
wx.lib.inspection.InspectionTool().Show() #inspection tool
app.MainLoop()

floatcanvas toolbar exact fit sample - worked in python 2, no longer working in 3 w/latest wxPython

The main issue is that the toolbar doesn't open anymore. The original goal was to hide/show the toolbar when the user leaves/enters the window/canvas along w/an exact fit app.
The following code worked w/FloatCanvas & Python 2:
#!/usr/bin/env python
import wx
# #import the installed version
# from wx.lib.floatcanvas import NavCanvas, FloatCanvas
# import a local version
import sys
sys.path.append("../")
from floatcanvas import NavCanvas, FloatCanvas
# Set a path to an Image file here:
ImageFile = "./white_tank.jpg"
class DrawFrame(wx.Frame):
"""
A frame used for the FloatCanvas Demo
"""
def __init__(self, *args, **kwargs):
# wx.Frame.__init__(self, *args, **kwargs)
wx.Frame.__init__(self, *args,
style=wx.FRAME_SHAPED
| wx.SIMPLE_BORDER
| wx.FRAME_NO_TASKBAR
)
# self.CreateStatusBar()
# Add the Canvas
self.CanvasNav = NavCanvas.NavCanvas(self,
ProjectionFun=None,
style=wx.TRANSPARENT_WINDOW,
# BackgroundColor=(255, 255, 255, 0),
# BackgroundColor="DARK SLATE BLUE",
)
self.CanvasNav.ToolBar.Hide()
Canvas = self.CanvasNav.Canvas
Canvas.MaxScale = 4
self.Canvas = Canvas
FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove)
self.CanvasNav.ZoomButton.Bind(wx.EVT_BUTTON, self.ZoomToFit)
# create the image:
image = wx.Image(ImageFile)
img = Canvas.AddScaledBitmap(image,
(0, 0),
Height=image.GetHeight(),
Position='tl',
Quality='normal',
)
self.image = image
self.set_size(image)
img.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnLeftDown)
img.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
self.Bind(wx.EVT_MOVE, self.OnMove)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
self.Show()
self.CanvasNav.ToolBar.Show()
Canvas.ZoomToBB(margin_adjust=1.2)
self.move_count = 0
def ZoomToFit(self, e):
self.set_size(self.image)
self.Canvas.ZoomToBB(margin_adjust=1.2)
def set_size(self, image):
"""
adjusts the size of the window to fit the aspect ration of the image
"""
# compute the aspect ratio of the image
w, h = image.GetSize()
ar = float(w) / float(h)
# check the size of this window
w, h = self.GetSize()
print w, h
# adjust the width to get the rigth aspect ratio
w = int(h * ar)
self.SetSize( (w, h) )
print "reset size to:"
print self.GetSize()
def OnEnter(self, e):
self.CanvasNav.ToolBar.Show()
self.CanvasNav.ToolBar.Realize()
def OnLeave(self, e):
self.CanvasNav.ToolBar.Hide()
self.CanvasNav.ToolBar.Realize()
def OnMove(self, event):
"""
Updates the status bar with the world coordinates
"""
# self.SetStatusText("%i, %i" % tuple(event.Coords))
def OnLeftDown(self, obj):
print "Left Mouse Clicked on ", obj
def OnMotion(self, obj):
print "mouse moving on image:", self.move_count
self.move_count += 1
app = wx.App(False)
F = DrawFrame(None, title="FloatCanvas Demo App", size=(700, 700))
app.MainLoop()
Here's my attempt to get it to work the same way using the latest Python & wxPython, but am having issues w/the toolbar (also getting KeyError on line 64).
#!/usr/bin/env python
import wx
# #import the installed version
# from wx.lib.floatcanvas import NavCanvas, FloatCanvas
# import a local version
import sys
sys.path.append("../")
try:
from floatcanvas import NavCanvas, FloatCanvas, Resources
except ImportError: # if it's not there locally, try the wxPython lib.
from wx.lib.floatcanvas import NavCanvas, FloatCanvas, Resources
# Set a path to an Image file here:
# ImageFile = "C:\\Users\\alex\\Downloads\\white_tank.jpg"
ImageFile = "white_tank.jpg"
class DrawFrame(wx.Frame):
"""
A frame used for the FloatCanvas Demo
"""
def __init__(self, *args, **kwargs):
# wx.Frame.__init__(self, *args, **kwargs)
wx.Frame.__init__(self, *args,
style=wx.FRAME_SHAPED
| wx.SIMPLE_BORDER
| wx.FRAME_NO_TASKBAR
)
# self.CreateStatusBar()
# Add the Canvas
self.CanvasNav = NavCanvas.NavCanvas(self,
ProjectionFun=None,
style=wx.TRANSPARENT_WINDOW,
# BackgroundColor=(255, 255, 255, 0),
# BackgroundColor="DARK SLATE BLUE",
)
self.CanvasNav.ToolBar.Hide()
Canvas = self.CanvasNav.Canvas
Canvas.MaxScale = 4
self.Canvas = Canvas
FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove)
self.CanvasNav.ZoomButton.Bind(wx.EVT_BUTTON, self.ZoomToFit)
# create the image:
image = wx.Image(ImageFile)
img = Canvas.AddScaledBitmap(image,
(0, 0),
Height=image.GetHeight(),
Position='tl',
# Quality='normal',
)
self.image = image
self.set_size(image)
img.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.OnLeftDown)
# img.Bind(FloatCanvas.EVT_FC_MOTION, self.OnMotion)
self.Bind(wx.EVT_MOVE, self.OnMove)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
self.Show()
self.CanvasNav.ToolBar.Show()
Canvas.ZoomToBB(margin_adjust=1.2)
self.move_count = 0
def ZoomToFit(self, e):
self.set_size(self.image)
self.Canvas.ZoomToBB(margin_adjust=1.2)
def set_size(self, image):
"""
adjusts the size of the window to fit the aspect ration of the image
"""
# compute the aspect ratio of the image
w, h = image.GetSize()
ar = float(w) / float(h)
# check the size of this window
w, h = self.GetSize()
print(w, h)
# adjust the width to get the rigth aspect ratio
w = int(h * ar)
self.SetSize( (w, h) )
print("reset size to:")
print(self.GetSize())
def OnEnter(self, e):
self.CanvasNav.ToolBar.Show()
self.CanvasNav.ToolBar.Realize()
def OnLeave(self, e):
self.CanvasNav.ToolBar.Hide()
self.CanvasNav.ToolBar.Realize()
def OnMove(self, event):
"""
Updates the status bar with the world coordinates
"""
# self.SetStatusText("%i, %i" % tuple(event.Coords))
def OnLeftDown(self, obj):
print("Left Mouse Clicked on ", obj)
def OnMotion(self, obj):
print("mouse moving on image:", self.move_count)
self.move_count += 1
app = wx.App(False)
F = DrawFrame(None, title="FloatCanvas Demo App", size=(700, 700))
app.MainLoop()
It is working using python 3.6.7 and wxpython 4.0.3 gtk2 but fails when using wxpython 4.0.1 gtk3. [On Linux]
You failed to specify your OS and the version of wx that you are using.
If we assume that you are using 4.0.1 gtk3 the answer is to change the style of the DrawFrame from
wx.Frame.__init__(self, *args,style=wx.FRAME_SHAPED| wx.SIMPLE_BORDER)| wx.FRAME_NO_TASKBAR)
to
wx.Frame.__init__(self, *args)
Note also that margin_adjust in Canvas.ZoomToBB(margin_adjust=1.2) is now invalid.
I would also worry about the fact that you are trying to import a local version of floatcanvas
sys.path.append("../")
try:
from floatcanvas import NavCanvas, FloatCanvas, Resources
except ImportError: # if it's not there locally, try the wxPython lib.
from wx.lib.floatcanvas import NavCanvas, FloatCanvas, Resources

Where to place mainloop call in my tkinter app code?

I've been trying to figure out how to use how to place a mainloop() in my GUI so I don't need to have a while(True) anymore.
At the bottom of my code is an example of how I've been using this code. I know this is a lot of code to go through, so any help is appreciated
# Imports
import os
import os.path
import sys
import tkinter
tk = tkinter
from tkinter import font
#----- Set flag for JPEG support ---
noJPEG = False
try:
from PIL import Image
Pimg = Image
from PIL import ImageTk
Pimgtk = ImageTk
from PIL import ImageDraw
except ImportError:
noJPEG = True
#-----------------------------------
#
# Create an invisible global parent window to hold all children.
# Allows for easy closing of all windows by mouse click. If not
# closed programmatically, program exits if all windows are
# manually closed.
_root = tk.Tk()
_root.withdraw()
###
class ImageView(tk.Canvas):
def __init__(self, image, title, lotDiogram):
is_Diogram = lotDiogram
master = tk.Toplevel(_root) #sets master as a subwindow of _root
master.protocol("WM_DELETE_WINDOW", self.close)
if(is_Diogram == True):
"""When 'is_Diogram' is True, it means image is a
parking lot diogram, and does need scrollbars
added to its Toplevel"""
tk.Canvas.__init__(self, master,
width = 650, height = 525,
scrollregion=(0,0,image.getWidth(),
image.getHeight()))
self.master.title(title)
# Scrollbar added to master
vsb = tk.Scrollbar(master, orient="vertical", command=self.yview)
hsb = tk.Scrollbar(master, orient="horizontal", command=self.xview)
self.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
self.grid(row=0, column=0, sticky="nswe")
vsb.grid(row=0, column=1, sticky="ns")
hsb.grid(row=1, column=0, sticky="ew")
master.grid_rowconfigure(0, weight=1)
master.grid_columnconfigure(0, weight=1)
master.minsize(width=600, height=500)
master.maxsize(width = image.getWidth(),
height = image.getHeight() )
self.foreground = "black"
self.image = image
self.height = image.getHeight()
self.width = image.getWidth()
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self.onClick)
self.tags = None
_root.update() #redraw global window
else:
"""When 'is_Diogram' is False, it means image is a
lot sign, and does not need scrollbars
added to its Toplevel"""
tk.Canvas.__init__(self, master, width = image.getWidth(),
height = image.getHeight() )
self.master.title(title)
self.pack()
master.resizable(0,0)
self.foreground = "black"
self.image = image
self.height = image.getHeight()
self.width = image.getWidth()
self.mouseX = None
self.mouseY = None
self.bind("<Button-1>", self.onClick)
self.tags = None
_root.update() #redraw global window
def close(self):
"""Close a window."""
self.master.destroy()
self.quit()
_root.update()
def getMouseXY(self):
"""Return a tuple with x,y position in the image of the
mouse click."""
self.mouseX = None
self.mouseY = None
while (self.mouseX == None) or (self.mouseY == None) :
self.update()
return ((self.mouseX,self.mouseY))
def onClick(self, event):
"""Perform various functions when mouse is clicked."""
self.mouseX = int(self.canvasx(event.x))
self.mouseY = int(self.canvasy(event.y))
def drawShape(self, shape, tagz=None, txt=None, tfont="TkDefaultFont",
color1=None, color2=None, coords=None, w=None, OL=None):
"""Draws a shape, assigns it a tag, and binds the tag to a
mouse click."""
self.tags = tagz
if shape == 'rectangle':
"""Only will accept 2 pairs of XY coordinates, X0, Y0, X1, Y1)"""
self.create_rectangle(coords, fill = color1,
activefill = color2, tags = tagz)
elif shape == 'polygon':
"""Polygon will accept a minimum of 4 XY coordinate pairs"""
self.create_polygon(coords, fill = color1,
activefill = color2, outline = OL,
tags = tagz)
else: #should never get here since shape is required
print("No shape specified!")
_root.update() #redraw global window
def getTags(self):
self.getMouseXY()
obj = self.find_closest(self.mouseX, self.mouseY)
return self.gettags(obj)
def getHeight(self):
"""Return the height of the window."""
return self.height
def getWidth(self):
"""Return the width of the window."""
return self.width
class imagesMSUError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
#------------------------------------------------------
class Image(object):
def __init__(self, file_or_type, *args):
global noJPEG
try:
if type(file_or_type) != str:
raise imagesMSUError(str(file_or_type))
except imagesMSUError as e:
print('imagesMSU_Error: "' + e.value + '" is not a '
+ 'valid image type or a valid GIF/JPG filename.')
sys.exit(1)
# If an image type was passed in, create a blank image
# of that type.
self.type = file_or_type.upper()
if self.type == 'GIF' or self.type == 'JPG':
# Create blank image; *args are width, height.
self.width, self.height = args
self.filename = 'blank' #default filename for saving
if self.type == 'GIF': #create blank gif
self.image = tk.PhotoImage(master =_root,
width = self.width,
height = self.height)
if self.type == 'JPG': #create blank jpg
try:
if noJPEG: #libjpeg not installed
raise imagesMSUError(noJPEG)
except imagesMSUError as e:
print('imagesMSU_Error: Support library for JPEGs '
+ 'not found. Use GIFs instead.')
sys.exit(1)
else:
self.image = Pimg.new(mode = "RGB",
size = (self.width, self.height))
else: #A filename was passed in. Validate then load into an image.
# Check for valid image type
self.type = file_or_type[-3:].upper() #file's 3 char extension
try:
if self.type != 'GIF' and self.type != 'JPG': #wrong extension
raise imagesMSUError(self.type)
except imagesMSUError as e:
print('imagesMSUError: "' + e.value
+ '" is not a valid image type.')
sys.exit(1)
# Check for a valid file
filename = file_or_type
try:
if not os.path.isfile(filename): #not a file or not found
raise imagesMSUError(filename)
except imagesMSUError as e:
print('imagesMSU_Error: File "' + e.value + '" not found.')
sys.exit(1)
if self.type == 'GIF':
self.image = tk.PhotoImage(file = filename, master = _root)
self.width = self.image.width()
self.height = self.image.height()
if self.type == 'JPG':
try:
if noJPEG: #libjpeg not installed
raise imagesMSUError(noJPEG)
except imagesMSUError as e:
print('imagesMSU_Error: Support library for JPEGs '
+ 'not found. Use GIFs instead.')
sys.exit(1)
else:
self.image = Pimg.open(filename)
box = self.image.getbbox()
self.width = box[2]
self.height = box[3]
def getType(self):
"""Returns the image type."""
return self.type
def getWidth(self):
"""Returns the width of the image in pixels."""
return self.width
def getHeight(self):
"""Returns the height of the image in pixels."""
return self.height
def draw(self, win):
"""Creates and opens a window on an image. The user must close
the window to return control to the caller."""
self.canvas = win
if self.type == 'GIF':
self.canvas.create_image(self.width // 2,
self.height // 2,
image = self.image)
if self.type == 'JPG':
self.photoImage = Pimgtk.PhotoImage(self.image)
self.canvas.create_image(self.width // 2,
self.height // 2,
image = self.photoImage)
# Update the hidden root window to draw the image.
_root.update()
#-------------Example program----------------------------#
def main():
# Load an image
img1 = Image('BURG.jpg')
# Draw the image in a window.
window1 = ImageView(img1, "Burg Lot", True)
img1.draw(window1)
window1.drawShape('rectangle', tagz="Handicap", coords=[391, 214, 429, 235],
color1=None, color2='red')
window1.drawShape('rectangle', tagz="Lot_Sign", coords=[486, 375, 509, 389],
color1=None, color2='red')
# Loop to click parking spots and display parking sign
while(True):
tags = window1.getTags()
for tag in tags:
if tag != 'current':
if tag == 'Handicap':
img2 = Image('BUR HC.jpg')
window2 = ImageView(img2, "Handicap", False)
img2.draw(window2)
if tag == 'Lot_Sign':
img2 = Image('Lot Sign.jpg')
window2 = ImageView(img2, "Lot Info", False)
img2.draw(window2)
if __name__ == '__main__':
main()
If I understand your question correctly, I would do the main-loop as:
https://docs.python.org/3/library/tkinter.html#a-simple-hello-world-program
and then refactor a bit.
Your current for tag in tags-loop can be done inside drawShape where you set the tags anyway. This will convert your program to be event driven.

Transparent panel doesn't updates its background after being moved/dragged in wxPython

I am working with python v2.7 and wxPython v3.0 on Windows 8 OS.
The code provided below simply creates a transparent panel named as myPanel that contains a button. The transparent panel is created on a mainPanel which contains an image as a background.
The transparent panel can be dragged around in the frame.
Problem: After dragging the transparent panel I observed that the background of the transparent panel is not updated automatically. How to update it automatically? How ever if I minimize the gui window and restore it again, the background of the transparent panel is updated automatically! I don't understand the reason of this affect?
I tried using Refresh(), Update() etc. in MouseUp(self, e) method, but unfortunately nothing helped.
Here are the screenshots of the app. The initial state is shown in the image below when the app starts:
After dragging the transparent panel, the background is not updated as shown in the image below:
After minimizing the app window and then restoring it, you'll notice that the background of the transparent panel is updated automatically as shown in the image below:
Code: The image used in the code can be downloaded from here. globe.jpg
import wx
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()
Thank you for your time!
You can create a custom panel and then draw a portion of the globe on that panel based on where it's located on top of the parent frame. This method "fakes" the transparency. I've included an example below.
import wx
class CustomPanel(wx.Panel):
def __init__(self,parent):
wx.Panel.__init__(self,parent,-1,size=(80,80))
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, evt):
parentw,parenth = self.GetParent().GetSize()
image = wx.Image('globe.jpg', wx.BITMAP_TYPE_ANY)
x,y = self.GetPosition()
mywidth,myheight = self.GetSize()
if x + mywidth >= parentw:
mywidth = parentw - x
if y + myheight >= parenth:
myheight = parenth - y
drawx = 0
drawy = 0
if x < 0:
drawx = abs(x)
x = 0
if y < 0:
drawy = abs(y)
y = 0
r = wx.Rect(x,y,mywidth,myheight)
try:
image = image.GetSubImage(r)
except:
# rectangle is out of parent
print 'rect ',r ,' is out of parent frame'
return
bitmap = image.ConvertToBitmap()
pdc = wx.PaintDC(self)
pdc.DrawBitmap(bitmap, drawx, drawy)
class gui(wx.Frame):
def __init__(self, parent, id, title):
self.d = d = {}
wx.Frame.__init__(self, None, id, title, size=(260,260), style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER | wx.CLIP_CHILDREN)
statusbar = self.CreateStatusBar()
self.mainPanel = mainPanel = wx.Panel(self)
self.mainSizer = mainSizer = wx.BoxSizer(wx.VERTICAL)
#self.myPanel = myPanel = wx.Panel(mainPanel, -1, style=wx.TRANSPARENT_WINDOW, size=(80,80))
self.myPanel = myPanel = CustomPanel(mainPanel)
button1 = wx.Button(myPanel, -1, size=(30,30), pos=(10,10))
button1.SetBackgroundColour('#fff111')
button2 = wx.Button(myPanel, -1, size=(30,30), pos=(40,40))
button2.SetBackgroundColour('#fff111')
mainSizer.Add(myPanel, 0, wx.ALL, 0)
myPanel.Bind(wx.EVT_LEFT_DOWN, self.MouseDown)
myPanel.Bind(wx.EVT_MOTION, self.MouseMove)
myPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
image_file = 'globe.jpg'
bmp1 = wx.Image(image_file, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(mainPanel, -1, bmp1, (0, 0))
mainPanel.Bind(wx.EVT_MOTION, self.MouseMove)
mainPanel.Bind(wx.EVT_LEFT_UP, self.MouseUp)
mainPanel.SetSizer(mainSizer)
mainPanel.Layout()
def MouseDown(self, e):
o = e.GetEventObject()
sx,sy = self.mainPanel.ScreenToClient(o.GetPositionTuple())
dx,dy = self.mainPanel.ScreenToClient(wx.GetMousePosition())
o._x,o._y = (sx-dx, sy-dy)
self.d['d'] = o
def MouseMove(self, e):
try:
if 'd' in self.d:
o = self.d['d']
x, y = wx.GetMousePosition()
o.SetPosition(wx.Point(x+o._x,y+o._y))
self.myPanel.Refresh()
except: pass
def MouseUp(self, e):
try:
if 'd' in self.d: del self.d['d']
except: pass
if __name__=='__main__':
app = wx.App()
frame = gui(parent=None, id=-1, title="Test")
frame.Show()
app.MainLoop()

wxpython Pan implementation issue

I'm trying to implement a panning function in a wxpython program. This program consists of a frame containing a ScrolledWindow which holds a Bitmap image.
Clicking and panning on the blank panel produces the expected behaviour. Clicking and panning on the image produces "jumping" of about 10px.
If the mouse_pos member is not updated during dragging, panning is smooth on the image but then panning on the blank panel is erroneous.
System information: wxpython 2.8.12.1, Python 2.7.3 64bit and Windows 7 64bit.
import wx
import inspect
import threading
class MyFrame(wx.Frame):
def __init__(self, parent, mytitle):
wx.Frame.__init__(self, parent, wx.ID_ANY, mytitle, size=(350, 350))
self.scrollwin = wx.ScrolledWindow(self, wx.ID_ANY)
width = 1000
height = 1000
self.scrollwin.SetScrollbars(20, 20, width/20, height/20)
self.scrollwin.SetScrollRate(1, 1)
self.scrollwin.Bind(wx.EVT_MOTION, self.on_mouse_drag)
self.scrollwin.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
image_file = "test.jpg"
image = wx.Bitmap(image_file)
sb = wx.StaticBitmap(self.scrollwin, wx.ID_ANY, image)
sb.Bind(wx.EVT_MOTION, self.event_defer)
sb.Bind(wx.EVT_LEFT_DOWN, self.event_defer)
self._mouse_pos = (0, 0)
self.sem = threading.Semaphore(0)
#property
def mouse_pos(self):
return self._mouse_pos
#mouse_pos.setter
def mouse_pos(self, value):
print "mouse_pos:"
print "Calling from: ", inspect.stack()[1][3]
print value
self._mouse_pos = value
def on_mouse_drag(self, event):
if event.Dragging() and event.LeftIsDown():
self.sem.release()
print self.sem._Semaphore__value
(mouse_x, mouse_y) = self.mouse_pos
#new_pos = self.scrollwin.CalcUnscrolledPosition(event.GetPosition()).Get()
#new_pos = self.scrollwin.CalcScrolledPosition(event.GetPosition()).Get()
new_pos = event.GetPositionTuple()
(new_x, new_y) = new_pos
scrollpos = self.scrollwin.GetViewStart()
(scroll_x, scroll_y) = scrollpos
(delta_x, delta_y) = ((new_x - mouse_x), (new_y - mouse_y))
(scroll_x, scroll_y) = ((scroll_x - delta_x), (scroll_y - delta_y))
print "Scroll:"
print (scroll_x, scroll_y)
self.scrollwin.Scroll(scroll_x, scroll_y)
#self.mouse_pos = self.scrollwin.CalcUnscrolledPosition(event.GetPosition()).Get()
#self.mouse_pos = self.scrollwin.CalcScrolledPosition(event.GetPosition()).Get()
self.mouse_pos = new_pos # Disabling this gives smooth panning on the image
self.sem.acquire(False)
def on_left_down(self, event):
#self.mouse_pos = self.scrollwin.CalcUnscrolledPosition(event.GetPosition()).Get()
#self.mouse_pos = self.scrollwin.CalcScrolledPosition(event.GetPosition()).Get()
self.mouse_pos = event.GetPositionTuple()
def event_defer(self, event):
event.ResumePropagation(1)
event.Skip()
app = wx.App()
MyFrame(None, "Test wx.ScrolledWindow()").Show()
app.MainLoop()
I found this by accident, but I tried running it and it looks great, no jumping when I'm panning an image around. I'm running python 2.7.3 on Ubuntu 12.04 (64bit), so maybe there's an issue with Windows but it does work great on Linux.

Categories