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()
Related
I've created a panel thats derived from wx.lib.scrolledpanel. I could scroll on it fine with my mousewheel until I put a grid into the panel. Now when the mouse cursor is on top of the grid, the scroll stopped working, and would start working again if i moved the cursor outside of the grid.
I figured the easiest solution after searching and searching was to just manually capture the mousewheel event and scroll the panel manually. I bound this handler to wx.EVT_MOUSEWHEEL inside my wx.App object
class Wx_app(wx.App):
def __init__(self):
super().__init__(clearSigInt=True)
self.frame = MyFrame(None, pos=(0,0), size=(1900, 1100))
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
def on_mouse_wheel(self, e):
# get the current scroll pos, is tuple with x as first val, y as second val
pos = self.frame.panel.CalcUnscrolledPosition(0, 0)
y_pos = pos[1]
# detemrine if user is scrolling up or down
if e.GetWheelRotation() > 0:
# user is scrolling up
print("UP")
self.frame.panel.Scroll(0, y_pos + 10)
else:
# user is scrolling down
print("DOWN")
self.frame.panel.Scroll(0, y_pos - 10)
This code works when i try to scroll down, but when i try to scroll back up with the the mousewheel nothing happens, even though "UP" registers in my terminal. Also i would think up should be y_pos - 10 and not y_pos + 10, but then the wheel scrolls in the opposite direction you would expect. What am I doing wrong? Perhaps i'm not getting the correct existing position in the first place, but CalcUnscrolledPosition is the only thing I could find that could maybe do that. I'm new to Python please explain it like I'm a 5 year old. thanks
Your immediate issue is that Up would be Y - 10 and Down would be Y + 10.
You need to go further up (less y) or further down (more y).
You also may well be comparing Apples with Oranges, when relying on the position as returned from the mouse event and how that relates to the position within the scrolled window.
The crux of this is that both the scrolledpanel and the grid are scrollable widgets.
Your issue seems to be that your grid is not of sufficent size to show the grid's scrollbars, thus causing confusion.
You can force the scrollbars on the grid and size it so that it is obvious that there are two sets of scrollbars and hopefully your users will work it out.
Here is a sample grid in a scrolled panel to play with (ignore the bound functions, they were there because initially I misunderstood your issue, thinking that you wanted manual scrolling within the grid)
import wx
import wx.lib.scrolledpanel
import wx.grid
class MyPanel(wx.Panel):
def __init__(self, *args, **kwargs):
wx.Panel.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
self.fileFormPanel = FileFormPanel(self)
self.sizer.Add(self.fileFormPanel, 1, wx.EXPAND)
self.SetSizer(self.sizer)
class FileFormPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self, parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent)
sizer = wx.BoxSizer(wx.VERTICAL)
# Create a wxGrid object
self.grid = wx.grid.Grid(self, -1, size=(300,480))
self.grid.CreateGrid(5, 10)
self.grid.SetRowLabelSize(1)
self.grid.SetDefaultColSize(120)
self.grid.SetColSize(0, 50)
self.grid.ShowScrollbars(True, True)
for j in range(5):
for k in range(10):
self.grid.SetCellValue(j, k, str(k))
text = wx.TextCtrl(self, wx.ID_ANY, value="Some text")
sizer.Add(self.grid)
sizer.Add(text)
self.SetSizer(sizer)
self.SetupScrolling()
#self.grid.Bind(wx.EVT_MOUSEWHEEL, self.OnGrid)
#self.Bind(wx.EVT_MOUSEWHEEL, self.OnScroll)
#self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnSelect)
# def OnGrid(self, event):
# obj = event.GetEventObject()
# if event.WheelRotation > 0:
# self.grid.MoveCursorUp(False)
# else:
# self.grid.MoveCursorDown(False)
#
# def OnSelect(self, event):
# obj = event.GetEventObject()
# r = event.GetRow()
# c = event.GetCol()
# self.grid.MakeCellVisible(r,c)
# event.Skip()
#
# def OnScroll(self, event):
# print("window scroll")
# event.Skip()
class DemoFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, size = (400, 400))
MyPanel(self)
class App(wx.App):
def OnInit(self):
self.frame = DemoFrame()
self.frame.Show()
self.SetTopWindow(self.frame)
return True
app = App()
app.MainLoop()
I am learning ui python and trying out wxpython for UI development (dont have UI exp either). I have been able to create a frame with a panel, a button and a text input box. I want to bring all these to center of the screen where they look properly, so that i can implement the rest of the functionality. currently they are overlapping
class KartScan(wx.Panel):
""" create a panel with a canvas to draw on"""
def __init__(self, parent):
wx.Panel.__init__(self, parent, wx.ID_ANY)
# pick a .jpg, .png, .gif, or .bmp wallpaper image file you
# have in the working folder or give full path
image_file = 'index.png'
self.bmp = wx.Bitmap(image_file)
# this 50ms delay is needed to allow image loading first
# may have to increase delay for very large images
wx.FutureCall(50, self.make_canvas)
# react to a resize event and redraw image
wx.EVT_SIZE(self, self.make_canvas)
# now put a button on the panel, on top of the wallpaper
sizer = wx.GridBagSizer()
self.entry = wx.TextCtrl(self, -1, value=u"Enter Waybill No.")
sizer.Add(self.entry, (0, 0), (1, 1), wx.EXPAND)
self.Bind(wx.EVT_TEXT_ENTER, self.OnPressEnter, self.entry)
button = wx.Button(self, -1, label="Add or Compare")
sizer.Add(button, (0, 1))
self.Bind(wx.EVT_BUTTON, self.OnButtonClick, button)
self.label = wx.StaticText(self, -1, label=u'This App is used !')
self.label.SetBackgroundColour(wx.WHITE)
self.label.SetForegroundColour(wx.BLUE)
sizer.Add(self.label, (1, 0), (1, 2), wx.EXPAND)
sizer.AddGrowableCol(0)
self.SetSizerAndFit(sizer)
self.SetSizeHints(-1, self.GetSize().y, -1, self.GetSize().y)
self.entry.SetFocus()
self.entry.SetSelection(-1, -1)
self.Show(True)
def make_canvas(self, event=None):
# create the paint canvas
dc = wx.ClientDC(self)
# forms a wall-papered background
# formed from repeating image tiles
brush_bmp = wx.BrushFromBitmap(self.bmp)
dc.SetBrush(brush_bmp)
# draw a rectangle to fill the canvas area
w, h = self.GetClientSize()
dc.DrawRectangle(0, 0, w, h)
def OnButtonClick(self, event):
self.label.SetLabel(self.entry.GetValue() + " You clicked the button !")
self.entry.SetFocus()
self.entry.SetSelection(-1, -1)
on click events
def OnPressEnter(self, event):
self.label.SetLabel(self.entry.GetValue() + " You pressed enter !")
self.entry.SetFocus()
self.entry.SetSelection(-1, -1)
The sizers do their layout work in the window's EVT_SIZE event. Since you are intercepting that event for your own thing then the sizer will not have a chance to do its work unless you help it.
One way to do it would be to call self.Layout() in the size event handler. Another way would be to call event.Skip() which will tell wx that the event should continue to be processed after your handler has returned. That allows the window's default size handler to still get the event and do the layout, and possibly other things depending on the type of the window class.
I have the following simple code (click the pink box and you can move it around with your mouse while holding down the left mouse button).
import wx
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
p = MovablePanel(self, -1)
self.i = 0
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
def OnPaint(self, event):
dc = wx.PaintDC(self)
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((500,500))
self.SetSize((500,500))
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
f.Maximize()
app.MainLoop()
and it is suppose to look like the following (simply resize the frame)
However after moving the pink box around you will see it really looks like this
I have tried the following
dc.Clear()
dc.DestroyClippingRegion()
wx.FULL_REPAINT_ON_RESIZE
wx.EVT_ERASE_BACKGROUND
I'm pretty sure it has to do with it being a panel, and therefore the PaintEvent only marking it partially damaged. This part is colored differently making the 'ghosting' or 'smearing' obvious. Perhaps I'm using the wrong words because I was unable to find a solution (and I this seems to be a non complex issue simply having to do with the 'damaged' region).
Ok I found the problem, but I'll try to post more details later.
Basically the goal of this code is to move a panel around and then update the parent panel. SetPosition calls Move which going through the wxWidget code calls DoMoveWindow, all of this leads to a change in position and a repaint call (not sure what calls the repaint yet). Great. However the repaint only marks a certain 'area' as it tries to be efficient. That is why some of the issue can be solved by having the panel go over the 'ghosted' area. What you have to do is after the SetPosition, call GetParent().Refresh(), which will send a 'full' paint without any excluded area.
Another thing to note is there are TWO terms for this 'damaged' or 'clipped' area. One is 'damage' however there is another, 'dirty'. Damage is used in the wx PaintDC information
Using wx.PaintDC within EVT_PAINT handlers is important because it
automatically sets the clipping area to the damaged area of the
window. Attempts to draw outside this area do not appear.
Trusting the documentation you will be mostly lost. However in one of the wxPython DoubleBuffer how to's the lingo changes (but it is the same thing as 'damage')
Now the OnPaint() method. It's called whenever ther is a pain event
sent by the system: i.e. whenever part of the window gets dirty.
Knowing this if you Google wx Window dirty you will get the following
Mark the specified rectangle (or the whole window) as "dirty" so it
will be repainted. Causes an EVT_PAINT event to be generated and sent
to the window.
Take the following three print cycles where an EVT_PAINT was fired after a SetPosition call (this is WITHOUT the GetParent().Refresh() call)
# first EVT_PAINT
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=500, h=501) # the only place getting update is
# directly below the panel
# (that is (500, 500) )
# second
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=0, y=6, w=910, h=845) # however this time the update area is
# bigger, this is also right before
# the move
# i believe what it is doing is
# drawing from (0,6) to (910, 851)
# why? because the panel is moving to
# (410, 390) and the bottom right
# corner of the panel (after moved)
# is (410+500, 390+461) = (910, 851)
# or about where the edge of the panel
# will be
# third
Drawing
Panel Size (1440, 851)
Clipping Rect (0, 0, 1440, 851)
Client Update Rect (x=410, y=390, w=500, h=461)
Here is the update code to play around with, hopefully this will help others.
import wx
instructions = """
How to use.
1) Hover your mouse over the pink panel.
2) Click down (left click)
3) While holding down drag mouse around
4) Release mouse button to stop.
"""
class AppPanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.settings_sizer = wx.BoxSizer(wx.HORIZONTAL)
p = MovablePanel(self, -1)
self.c = wx.CheckBox(self, -1, label = "Ghosting On?")
self.p = p
self.i = 0
self.settings_sizer.Add(self.c)
self.sizer.Add(self.settings_sizer)
self.sizer.Add(self.p)
self.SetSizer(self.sizer)
self.Layout()
self.Bind(wx.EVT_CHECKBOX, self.OnCheck, self.c)
self.Bind(wx.EVT_PAINT, self.OnPaint, self)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase, self)
def OnCheck(self, event):
print "CHECK\n\n\n\n\n"
v = self.c.GetValue()
self.p.r = v
print v
def OnErase(self, event):
pass
def OnPaint(self, event):
print "Drawing"
dc = wx.PaintDC(self)
print "Panel Rect, ", self.p.GetPosition(),
print self.p.GetSize()
print "Clipping Rect", dc.GetClippingBox()
print "Client Update Rect", self.GetUpdateClientRect()
print "----------------------------"
self.i = self.i+10
c = self.i % 255
c = (0, 0, c)
dc.SetPen(wx.Pen(c))
dc.SetBrush(wx.Brush(c))
dc.DrawRectangle(0, 0, 10000,10000)
self.SetBackgroundColour(c)
dc.SetPen(wx.Pen("WHITE"))
dc.SetBrush(wx.Brush("WHITE"))
dc.DrawRectangle(0, 0, self.GetSize()[0], self.c.GetSize()[1])
class MovablePanel(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.SetMinSize((300,300))
self.SetSize((300,300))
txt = wx.StaticText(self, -1, label = "CLICK AND DRAG ME!")
inst = wx.StaticText(self, -1, label = instructions)
font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)
txt.SetFont(font)
inst.SetFont(font)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(txt, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
sizer.Add(inst, flag = wx.ALIGN_CENTRE_VERTICAL | wx.ALIGN_CENTRE_HORIZONTAL)
self.SetSizer(sizer)
self.SetBackgroundColour("PINK")
self.LEFT_DOWN = False
self.r = False
self.Bind(wx.EVT_MOTION, self.OnMove, self)
self.Bind(wx.EVT_LEFT_DOWN,
self.OnClickDown,
self)
self.Bind(wx.EVT_LEFT_UP,
self.OnClickUp,
self)
def OnClickUp(self, event):
self.LEFT_DOWN = False
self.Refresh()
def OnClickDown(self, event):
self.LEFT_DOWN = True
self.Refresh()
def OnMove(self, event):
if self.LEFT_DOWN:
p = self.GetTopLevelParent().ScreenToClient(wx.GetMousePosition())
self.SetPosition(p)
if not self.r:
self.GetParent().Refresh()
if __name__ == "__main__":
app = wx.App(False)
f = wx.Frame(None, -1, size = (700, 700))
p = AppPanel(f, -1)
f.Show()
app.MainLoop()
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)
Does anybody know how i would go about forcing a panel to be square.
The case where this occurs is have a panel in which I have a horizontal BoxSizer with 2 slots, In the left slot i have a panel that I am going to be drawing to though wx.PaintDC, on the right I am going to have a a list control or some other widget.
What i am trying to achieve is to have the window realizable and to have the left panel always stay square, and to have the right hand content fill the rest of the space.
One of the solutions is to use EVT_SIZE to respond to window resizing and update panel size in the event function. Simple example code:
import wx
from wx.lib.mixins.inspection import InspectionMixin
class MyApp(wx.App, InspectionMixin):
def OnInit(self):
self.Init() # initialize the inspection tool
frame = wx.Frame(None)
sizer = wx.BoxSizer(wx.HORIZONTAL)
frame.SetSizer(sizer)
self.__squarePanel = wx.Panel(frame)
sizer.Add(self.__squarePanel, 0, wx.ALL | wx.EXPAND, 5)
frame.Bind(wx.EVT_SIZE, self.OnSize)
frame.Show()
self.SetTopWindow(frame)
return True
def OnSize(self, evt):
frame = evt.GetEventObject()
frameW, frameH = frame.GetSize()
targetSide = min(frameW, frameH)
self.__squarePanel.SetSize((targetSide, targetSide))
app = MyApp()
app.MainLoop()
You can bind to wx.EVT_SIZE to resize the panel when the window is resized. Partial code (untested, but someting like this):
self.panel = wx.Panel(self, -1, size=(200, 200))
self.Bind(wx.EVT_SIZE, self.resize_panel)
def resize_panel():
w, h = self.sizer.GetSize()
w = h
panel.SetSize(w, h)