I'm trying to build an application where the user can drag&drop some button around the panel.
I first have an error about a mouse capture event lost and I finally found that I have to catch this event to prevent the error.
But now, when I run the application, I can drag&drop the button however the application is totally frozen after I release the mouse's left button.
I have to stop it with Ctrl+C from the terminal otherwise my mouse is unusable in any other windows in my desktop environment.
I suspect a problem of mouse capturing event that is not well handled.
I'm working under Ubuntu 16.04 with Python 3.5 installed from package (apt).
I tried with both wxPython 4.0.0 installed from package (apt) and also with the latest wxPython 4.0.4 installed from pip.
In both cases the application is totally frozen after a click or a drag&drop of the button.
import wx
class DragButton(wx.Button):
def __init__(self, parent, id=wx.ID_ANY, label="", pos=(0, 0)):
super().__init__(parent=parent, id=id, label=label, pos=pos)
self._dragging = False
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, lambda evt: None)
def OnLeftDown(self, evt):
print("Left down")
if not self.HasCapture():
self.CaptureMouse()
x, y = self.ClientToScreen(evt.GetPosition())
originx, originy = self.GetPosition()
dx = x - originx
dy = y - originy
self.delta = ((dx, dy))
def OnLeftUp(self, evt):
print("Left UPPPP")
if self.HasCapture():
self.ReleaseMouse()
def OnMouseMove(self, evt):
if evt.Dragging() and evt.LeftIsDown():
x, y = self.ClientToScreen(evt.GetPosition())
fp = (x - self.delta[0], y - self.delta[1])
self.Move(fp)
class GDomFrame(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title, size=(350, 300))
self._init_ui()
self.Centre()
def _init_ui(self):
panel = wx.Panel(self)
self.button = DragButton(panel, label="Drag me", pos=(10, 10))
if __name__ == '__main__':
print("wxPython version: {}".format(wx.__version__))
app = wx.App()
ex = GDomFrame(None, title='GDom Application')
ex.Show()
app.MainLoop()
With this code I expect to have a button that I can move around the panel several time.
I have tested a similar script. It works fine on Windows, but not on ubuntu 16.04. I solved the problem like this.
def OnLeftDown(self, evt):
print("Left down")
if not self.HasCapture():
self.CaptureMouse()
self.ReleaseMouse() # <------
My script:
import wx
class Mywin(wx.Frame):
def __init__(self, parent, title):
super(Mywin, self).__init__(parent, title = title,size = (400,200))
self.InitUI()
self.Centre()
def InitUI(self):
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
self.btn = wx.Button(self.panel,-1,"click Me",pos=(10, 10))
vbox.Add(self.btn,0,wx.ALIGN_CENTER)
self.btn.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
self.btn.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.btn.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
print ("Init pos:",self.btn.GetPosition())
def OnMouseDown(self, event):
if (not self.btn.HasCapture()):
self.btn.CaptureMouse()
self.btn.ReleaseMouse()
sx,sy = self.panel.ScreenToClient(self.btn.GetPosition())
dx,dy = self.panel.ScreenToClient(wx.GetMousePosition())
self.btn._x,self.btn._y = (sx-dx, sy-dy)
def OnMouseMove(self, event):
if event.Dragging() and event.LeftIsDown():
x, y = wx.GetMousePosition()
self.btn.SetPosition(wx.Point(x+self.btn._x,y+self.btn._y))
print(self.btn.GetPosition())
def OnMouseUp(self, event):
if (self.btn.HasCapture()):
self.btn.ReleaseMouse()
print ("Final pos:",self.btn.GetPosition())
def main():
app = wx.App()
w = Mywin(None, title='Button demo')
w.Show()
app.MainLoop()
if __name__ == '__main__':
main()
Related
i have a little GUI app, which runs on Windows 7 and 10 well. With self.Move(x,y) I can move the window, if x, or y is negative, part of my window or the whole window is out of screen. I want to achieve this behavior on Linux. On ubuntu 16.04 I tried on unity and kde, but it didnt work, the whole window is always visible. Can you show me way to move my window out of screen on linux?
import wx
class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# kwds["pos"] = (10,10)
self.frame = wx.Frame.__init__(self, *args, **kwds)
self.SetTitle("Move around the screen")
self.InitUI()
def InitUI(self):
self.location1 = wx.Point(-30,-100)
self.location2 = wx.Point(500,500)
self.panel1 = wx.Panel(self)
self.button1 = wx.Button(self.panel1, -1, label="Move", size=(80,25), pos=(10,10))
self.button1.Bind(wx.EVT_BUTTON, self.OnItem1Selected)
self.Show()
self.Move(self.location1)
def OnItem1Selected(self, event):
self.MoveAround()
def MoveAround(self):
curr_location = self.GetPosition() #or self.GetPositionTuple()
if curr_location == self.location1:
print ("moving to ", self.location2)
self.Move(self.location2)
else:
print ("moving to ", self.location1)
self.Move(self.location1)
if __name__ == '__main__':
app = wx.App()
frame = MainFrame(None)
app.MainLoop()
I'm using wx.Bitmap's GetSubBitmap() API. On linux (ubuntu, fedora) everything OK. Trying to port code to windows7, GetSubBitmap() return empty bitmap, which result in black bitmap.
Here is simple example that draws periodically green rectangle somewhere on panel. On windows7 it turn to black rectangle.
import wx
import random
class mypanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.buffer = wx.EmptyBitmap(700, 500)
self.SetBackgroundColour("BLUE")
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.ID_TIMER = 1
self.timer = wx.Timer(self, self.ID_TIMER)
self.Bind(wx.EVT_TIMER, self.OnTimer, id=self.ID_TIMER)
self.timer.Start(1000, wx.TIMER_CONTINUOUS)
self.BufferPaint()
self.Refresh(False)
def OnTimer(self, event):
print "OnTimerEvent " + str(event.Id)
self.BufferPaint()
def BufferPaint(self):
dc = wx.MemoryDC()
dc.SelectObject(self.buffer)
dc.SetBackground(wx.Brush("green"))
dc.Clear()
sub = self.buffer.GetSubBitmap(wx.Rect(5,5,30,30))
dc.SetBackground(wx.Brush("red"))
dc.Clear()
dc.DrawBitmap(sub, random.randint(0,600), random.randint(0,400))
self.Refresh(False)
def OnPaint(self, event=None):
dc = wx.BufferedPaintDC(self, self.buffer)
class myframe(wx.Frame):
"""Draw a line to a panel."""
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Draw on Panel", size=(800,600))
self.panel1 = mypanel(self)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel1, 2, wx.EXPAND)
self.SetSizer(self.sizer)
app = wx.App(False)
frame = myframe(None)
frame.Show()
app.MainLoop()
def BufferPaint(self):
dc = wx.MemoryDC()
dc.SelectObject(self.buffer)
dc.SetBackground(wx.Brush("red"))
dc.Clear()
dc.SetBrush(wx.Brush("green"))
dc.DrawRectangleRect( wx.Rect(random.randint(0,600),random.randint(0,400),30,30) )
self.Refresh(False)
I modified BufferPaint in your sample code to use DrawRectangleRect so that we explicitly paint with "green". Your code may be relying on default behavior how GetSubBitmap inherit the color which may be platform dependent.
My edit worked on my win7 machine.
Python 2.7, WxPython 3.0.2
We are trying to automatically close an entire program under certain conditions. For various reasons, we can't just kill the process. We've had some level of success with it. We can close it if there's no modal dialogs, or a single modal dialog. Once we introduce the second modal dialog (nested), it fails to stop properly.
The actual error received appears to be:
wx._core.PyAssertionError: C++ assertion "IsRunning()" failed at ..\..\src\common\evtloopcmn.cpp(83) in wxEventLoopBase::Exit(): Use ScheduleExit() on not running loop
Here's a working example of our issue. The frame will automatically close after 5 seconds. Clicking the button will load a dialog. Clicking the button on the dialog will open another dialog. It works fine until the last dialog is opened.
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__myThread = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
if isinstance(lst[i], wx.Dialog):
print "Closing " + str(lst[i])
lst[i].Close(True)
#lst[i].Destroy()
def __waitThenClose(self):
for x in range(0, 5):
print "Sleeping..."
sleep(1)
self.closeOpenDialogs()
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.ShowModal()
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, None, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __on_btn_cancel(self, event):
self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
I think what is happening here is that the first call to ShowModal() blocks the at the app level (not just the frame level) which is preventing the second dialog from becoming fully initialized. To work around this issue I would call Show() instead of ShowModal() and add wx.FRAME_FLOAT_ON_PARENT to the dialog style flags. You can also call Disable() on the parts of the program you don't want the user to interact with while the dialogs are open.
EDIT: Here is a working example:
from threading import Thread
from time import sleep
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="TEST", size=(400, 400))
self.Show()
self.__someDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
self.__myThread = Thread(target=self.__waitThenClose, name="Closer")
self.__myThread.setDaemon(True)
self.__myThread.start()
def __onOK(self, evt):
self.__someDialog = SomeDialog(self)
self.__someDialog.ShowModal()
def closeOpenDialogs(self, evt=None):
lst = wx.GetTopLevelWindows()
for i in range(len(lst) - 1, 0, -1):
dialog = lst[i]
if isinstance(dialog, wx.Dialog):
print "Closing " + str(dialog)
# dialog.Close(True)
wx.CallAfter(dialog.Close)
# sleep(1)
# dialog.Destroy()
def __waitThenClose(self):
for x in range(0, 10):
print "Sleeping..."
sleep(1)
wx.CallAfter(self.closeOpenDialogs)
wx.CallAfter(self.Close, True)
class SomeDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Some Dialog')
self.SetSize((300, 300))
self.__anotherDialog = None
self.__okButton = wx.Button(self, -1, "Press me")
self.Bind(wx.EVT_BUTTON, self.__onOK)
wx.EVT_CLOSE(self, self.__on_btn_cancel)
def __onOK(self, evt):
self.__anotherDialog = AnotherDialog(self)
self.__anotherDialog.SetWindowStyleFlag(
wx.FRAME_FLOAT_ON_PARENT|wx.DEFAULT_DIALOG_STYLE)
self.__anotherDialog.Show()
def __on_btn_cancel(self, event):
event.Skip()
self.EndModal(wx.ID_CANCEL)
class AnotherDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id=-1, title='Another Dialog')
self.SetSize((200, 200))
wx.EVT_CLOSE(self, self.__on_btn_cancel)
parent.Disable()
def __on_btn_cancel(self, event):
event.Skip()
self.GetParent().Enable()
# self.EndModal(wx.ID_CANCEL)
if __name__ == "__main__":
app = wx.App()
mainFrame = MainFrame()
app.MainLoop()
The only way to reliably gracefully close all the modal dialogs, whether they were explicitly opened by your own code or not, is to use wxModalDialogHook to remember all the opened dialogs and then close them all, in the reverse (i.e. LIFO) order, before quitting the application.
Unfortunately I don't know if wxModalDialogHook is available in Python.
This code reads a picture and put it as background in a window.
There are two issues I cannot explain:
once you import the picture, clicking the red "X" in the top-right corner doesn't close the window.
if you try to drag the image, the program crashes.
Why is it so?
Thank you
import wx
import wx.lib.buttons as buttons
class Main(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, id=-1, title=title, size=(300, 300))
self.initUI()
self.panel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, size=(10000, 10000))
self.backGroundImage=''
self.Layout()
def initUI(self):
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fileMenu.AppendSeparator()
imp = wx.Menu()
importBackgroundButton = imp.Append(wx.ID_ANY, 'Import background')
self.Bind(wx.EVT_MENU, self.OnImportBackground, importBackgroundButton)
fileMenu.AppendMenu(wx.ID_ANY, 'I&mport', imp)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.SetTitle('test')
self.Centre()
self.Show(True)
#load background
def OnImportBackground(self, e):
app = wx.App(None)
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
dialog = wx.FileDialog(None, 'Open', wildcard='*.png', style=style)
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = None
dialog.Destroy()
self.backgroundImage = ButtonImage(self, self.panel, path, (0, 0))
W = self.backgroundImage.bmp.GetSize()[0]
H = self.backgroundImage.bmp.GetSize()[1]
self.SetSize((W+16, H+58))
self.Refresh()
#crash
class ButtonImage():
def __init__(self, parent, panel, nameImage, pos):
self.panel = panel
self.bmp = wx.Bitmap(nameImage, wx.BITMAP_TYPE_ANY)
self.maxPiecePositionX = self.panel.GetSize()[0] - self.bmp.GetSize()[0]
self.maxPiecePositionY = self.panel.GetSize()[1] - self.bmp.GetSize()[1]
self.bmapBtn = wx.BitmapButton(self.panel, id=wx.ID_ANY, bitmap=self.bmp, style=wx.NO_BORDER, pos=pos)
self.bmapBtn.Bind(wx.EVT_LEFT_DOWN, self.OnClickDown, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_LEFT_UP, self.OnClickUp, self.bmapBtn)
self.bmapBtn.Bind(wx.EVT_MOTION, self.MoveButton, self.bmapBtn)
self.hold = 0
self.holdPosition = (0, 0)
def EnterButton(self, event):
pass
def LeaveButton(self, event):
self.hold = 0
def OnClickDown(self, event):
obj = event.GetEventObject()
self.hold = 1
self.holdPosition = (event.GetX(), event.GetY())
def OnClickUp(self, event):
self.hold = 0
def MoveButton(self, event):
deltaX, deltaY = 0, 0
if self.hold:
deltaX = event.GetPosition()[0] - self.holdPosition[0]
deltaY = event.GetPosition()[1] - self.holdPosition[1]
newPositionX = self.bmapBtn.GetPosition()[0] + deltaX
newPositionY = self.bmapBtn.GetPosition()[1] + deltaY
if (0 < newPositionX < self.maxPiecePositionX) and (0 < newPositionY < self.maxPiecePositionY):
self.bmapBtn.SetPosition((newPositionX, newPositionY))
else:
self.holdPosition = self.holdPosition[0] + deltaX, self.holdPosition[1] + deltaY
self.bmapBtn.Raise()
self.bmapBtn.Refresh()
app = wx.App()
frame = Main(None, "Test")
frame.Show()
app.MainLoop()
This example has so many issues that it would take a long time to explain it. E. g., you create a new ButtonImage, which is essentially a wx.BitmapButton, every time you call OnImportBackground without destroying the old one, stacking up a collection of bitmap buttons without properly layouting them.
But what is driving the nail into the coffin that you instantiate a new wx.App every time OnImportBackground is called. If you remove this line (which is completely pointless), the frame can at least be closed.
But to see for "the right way (TM)" to do it, look at this stackoverflow post.
I'working on this nice example that shows a webcam output in a GTK widget with python and GStreamer:
http://pygstdocs.berlios.de/pygst-tutorial/webcam-viewer.html
here is the code:
#!/usr/bin/env python
import sys, os
import pygtk, gtk, gobject
import pygst
pygst.require("0.10")
import gst
class GTK_Main:
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.set_title("Webcam-Viewer")
window.set_default_size(500, 400)
window.connect("destroy", gtk.main_quit, "WM destroy")
vbox = gtk.VBox()
window.add(vbox)
self.movie_window = gtk.DrawingArea()
vbox.add(self.movie_window)
hbox = gtk.HBox()
vbox.pack_start(hbox, False)
hbox.set_border_width(10)
hbox.pack_start(gtk.Label())
self.button = gtk.Button("Start")
self.button.connect("clicked", self.start_stop)
hbox.pack_start(self.button, False)
self.button2 = gtk.Button("Quit")
self.button2.connect("clicked", self.exit)
hbox.pack_start(self.button2, False)
hbox.add(gtk.Label())
window.show_all()
# Set up the gstreamer pipeline
self.player = gst.parse_launch ("v4l2src ! autovideosink")
bus = self.player.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect("message", self.on_message)
bus.connect("sync-message::element", self.on_sync_message)
def start_stop(self, w):
if self.button.get_label() == "Start":
self.button.set_label("Stop")
self.player.set_state(gst.STATE_PLAYING)
else:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def exit(self, widget, data=None):
gtk.main_quit()
def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
elif t == gst.MESSAGE_ERROR:
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.player.set_state(gst.STATE_NULL)
self.button.set_label("Start")
def on_sync_message(self, bus, message):
if message.structure is None:
return
message_name = message.structure.get_name()
if message_name == "prepare-xwindow-id":
# Assign the viewport
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_xwindow_id(self.movie_window.window.xid)
GTK_Main()
gtk.gdk.threads_init()
gtk.main()
What I'd like to do is have a method to take a snapshot of the current frame and save to disk.
I think there are 2 ways to do it:
- some gstreamer method (but i think I should at least modify the pipeline)
- grab the picture somehow with GTK itself
Any hint on this?
I have no experience with gstreamer or gtk, any help is really appreciated
Thanks a lot
Mauro
Thanks to OpenCV I managed to rewrite everything with wxPython (which i know better than pyGTK). Here is a full working example (whith snapshot!), if anyone interested.
Also checkout the OpenCV wiki here: http://opencv.willowgarage.com/wiki/wxpython
import wx
import opencv.cv as cv
import opencv.highgui as gui
class CvMovieFrame(wx.Frame):
TIMER_PLAY_ID = 101
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1,)
sizer = wx.BoxSizer(wx.VERTICAL)
self.capture = gui.cvCreateCameraCapture(0)
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.SetSize((frame.width + 300, frame.height + 100))
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.displayPanel= wx.StaticBitmap(self, -1, bitmap=self.bmp)
sizer.Add(self.displayPanel, 0, wx.ALL, 10)
self.shotbutton = wx.Button(self,-1, "Shot")
sizer.Add(self.shotbutton,-1, wx.GROW)
self.retrybutton = wx.Button(self,-1, "Retry")
sizer.Add(self.retrybutton,-1, wx.GROW)
self.retrybutton.Hide()
#events
self.Bind(wx.EVT_BUTTON, self.onShot, self.shotbutton)
self.Bind(wx.EVT_BUTTON, self.onRetry, self.retrybutton)
self.Bind(wx.EVT_PAINT, self.onPaint)
self.Bind(wx.EVT_CLOSE, self.onClose)
self.playTimer = wx.Timer(self, self.TIMER_PLAY_ID)
wx.EVT_TIMER(self, self.TIMER_PLAY_ID, self.onNextFrame)
self.fps = 8;
self.SetSizer(sizer)
sizer.Layout()
self.startTimer()
def startTimer(self):
if self.fps!=0: self.playTimer.Start(1000/self.fps)#every X ms
else: self.playTimer.Start(1000/15)#assuming 15 fps
def onRetry(self, event):
frame = gui.cvQueryFrame(self.capture)
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.startTimer()
self.shotbutton.Show()
self.retrybutton.Hide()
self.hasPicture = False
self.Layout()
event.Skip()
def onShot(self, event):
frame = gui.cvQueryFrame(self.capture)
self.playTimer.Stop()
gui.cvSaveImage("foo.png", frame)
self.hasPicture = True
self.shotbutton.Hide()
self.retrybutton.Show()
self.Layout()
event.Skip()
def onClose(self, event):
try:
self.playTimer.Stop()
except:
pass
self.Show(False)
self.Destroy()
def onPaint(self, evt):
if self.bmp:
self.displayPanel.SetBitmap(self.bmp)
evt.Skip()
def onNextFrame(self, evt):
frame = gui.cvQueryFrame(self.capture)
if frame:
cv.cvCvtColor(frame, frame, cv.CV_BGR2RGB)
self.bmp = wx.BitmapFromBuffer(frame.width, frame.height, frame.imageData)
self.Refresh()
evt.Skip()
if __name__=="__main__":
app = wx.App()
f = CvMovieFrame(None)
f.Centre()
f.Show(True)
app.MainLoop()
I'm pretty sure that you could do:
self.movie_window.window.get_image(0, 0, 500, 400)
To get a GtkImage with the last frame from the webcam. The 500 and 400 is the width and the height of the window.