I would like to use the middle mouse button to drag an image in an application written in Python and using PythonCard/wxPython for the GUI.
The latest version of PythonCard only implements a "left mouse button drag" event and I am trying to modify PythonCard to handle a "middle mouse button drag" as well.
Here is the relevant code from Lib\site-packages\PythonCard\event.py :
class MouseMoveEvent(MouseEvent, InsteadOfTypeEvent):
name = 'mouseMove'
binding = wx.EVT_MOTION
id = wx.wxEVT_MOTION
def translateEventType(self, aWxEvent):
if aWxEvent.Dragging():
return MouseDragEvent.id
else:
return self.id
class MouseDragEvent(MouseMoveEvent):
name = 'mouseDrag'
id = wx.NewEventType()
class MouseMiddleDragEvent(MouseMoveEvent): #My addition
name = 'mouseMiddleDrag'
id = wx.NewEventType()
My addition does not work. What can I do instead? Is there a specific wxPython method that I could use to bypass PythonCard?
It turns out the the mouseDrag event is active regardless of which button on the mouse is pressed. To filter the middle mouse button, you need to call the MiddleIsDown() method from the MouseEvent.
def on_mouseDrag( self, event ):
do_stuff()
if event.MiddleIsDown():
do_other_stuff()
Related
I was having trouble formatting the title of this question, because I wasn't sure I'm going about this the right way, so let me explain.
I want to try and add a right-click context menu to an existing program for which I don't have the source code. wxPython is generally my framework of choice. I figured there was a couple ways of doing this:
1) Create a transparent wx.Frame which is tied to and sits on top of the existing program, intercepting mouse events. If I did this, I wasn't sure if the mouse events could then be passed to the underlying window. I like this option, because it would allow adding more useful information in the overlay.
2) Create a headless program which globally intercepts right-click events, and spawns the context menu at the pointer location when certain conditions are met. Based on the research I've done so far, this didn't seem possible without continuously polling for mouse position.
What am I missing? Is there a more elegant solution for this? Is this even possible using Python?
edit: I have a partial proof-of-concept working which looks like this:
import wx
import win32gui
import win32api
import win32con
class POC_Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='POC', pos=(0,0), size=wx.Size(500, 500), style=wx.DEFAULT_FRAME_STYLE)
self.ToggleWindowStyle(wx.STAY_ON_TOP)
extendedStyleSettings = win32gui.GetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE,
extendedStyleSettings | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)
win32gui.SetLayeredWindowAttributes(self.GetHandle(), win32api.RGB(0,0,0), 100, win32con.LWA_ALPHA)
self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.onRightUp)
self.CaptureMouse()
def onRightDown(self, event):
print(event)
def onRightUp(self, event):
print(event)
app = wx.App(False)
MainFrame = POC_Frame(None)
MainFrame.Show()
app.MainLoop()
This seems to work OK, as it passes the right click events to the underlying window, while still recognizing them, but it only does it exactly once. As soon as it loses focus, it stops working and nothing I've tried to return focus to it seems to work.
I've always had better luck hooking global mouse and keyboard events with pyHook rather than wx. Here is a simple example:
import pyHook
import pyHook.cpyHook # ensure its included by cx-freeze
class ClickCatcher:
def __init__(self):
self.hm = None
self._is_running = True
self._is_cleaned_up = False
self._is_quitting = False
self.set_hooks()
# this is only necessary when not using wx
# def send_quit_message(self):
# import ctypes
# win32con_WM_QUIT = 18
# ctypes.windll.user32.PostThreadMessageW(self.pump_message_thread.ident, win32con_WM_QUIT, 0, 0)
def __del__(self):
self.quit()
def quit(self):
if not self._is_running:
return
self._is_quitting = True
self._is_running = False
if self.hm:
# self.hm.UnhookKeyboard()
self.hm.UnhookMouse()
# self.send_quit_message()
self._is_cleaned_up = True
def set_hooks(self):
self._is_running = True
self._is_cleaned_up = False
self.hm = pyHook.HookManager()
self.hm.MouseRightUp = self.on_right_click
# self.hm.HookKeyboard()
self.hm.HookMouse()
def on_right_click(self):
# create your menu here
pass
If you weren't using wx, you'd have to use pythoncom.PumpMessages to push mouse and keyboard events to you program, but App.Mainloop() accomplishes the same thing (if you use PumpMessages and Mainloop together about half of the events won't be push to your program).
Creating a wx.Menu is easy enough. You can find the mouse coordinates using wx.GetMousePosition()
I have two buttons (Eg. A andB) which do the same things (based on user selection). So when you select something, then click the button, the selection's name will be input into the line-edit for the button. For example, if I click on buttonA, the input will be to lineEditA.
Currently I have created a signal function as follows:
def _connections_setup(self):
self.btnA.clicked.connect(self.get_sel_nameA)
self.btnB.clicked.connect(self.get_sel_nameB)
def get_sel_nameA(self):
sel_name = get_name_from_sel()
self.line_editA.setText(sel_name)
def get_sel_nameB(self):
sel_name = get_name_from_sel()
self.line_editA.setText(sel_name)
"""
def get_sel_name(self):
# Returns me a blank
button = self.sender()
print button.objectName()
# My objective here would be, if btnA is clicked, the sel_name will be inputted into lineEditA. Likewise for btnB
"""
Instead of creating two similar functions, how can I determine which button was clicked and have the selection's name to be input correctly into the line-edit?
I tried using self.sender() (see get_sel_name()) but it does not seems to return me the button name.
The sender() function only works in slots directly connected to signals. So your code needs to look something like this:
def _connections_setup(self):
self.btnA.clicked.connect(self.get_sel_name)
self.btnB.clicked.connect(self.get_sel_name)
def get_sel_name(self):
button = self.sender()
name = button.objectName()
if button is self.btnA:
self.line_editA.setText(name)
elif button is self.btnB:
self.line_editB.setText(name)
i have created a class in python that extends the tkinter canvas. I am trying to attach an event to this canvas to handle click's within the class. It functions if i attach the event outside of the class itself but when binding within the class the click event only occur's once and then proceeds not to do anything at all only performing the first click:
class myCanvas(Canvas):
def callback(event):
print('clicked at', event.x, event.y)
def __init__(self, parent, **kwargs):
Canvas.__init__(self, parent, **kwargs)
self.bind("<Button-1>", self.callback())
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
Binding the event functions correctly only if i attach the event outside of the class. Any help in finding a way to attach the event to the extended canvas would be appreciated.
The problem is in this line:
self.bind("<Button-1>", self.callback())
You need to connect something callable (in other words, a function) to the event. The function is referenced as self.callback. If you call the function (self.callback()) then you're connecting the return value of self.callback() to the click event instead of the function itself.
I have a layout with 5 buttons which I act as "menus", so you click on one button and one view will show up, you click another button and another view shows up. I need to find out which button is clicked so I can do something based on which button is pressed. Something like
if button1_is_clicked:
do_something()
else:
do_something_else()
What would be the best way to approach this?
Here is my code:
I want to be able to change the stylesheet of the button, so an active state and a non-active state
from PySide import QtCore
from PySide import QtGui
import VulcanGui
#--------------------------------------------------------------------------
class Program(QtGui.QMainWindow, VulcanGui.Ui_MainWindow):
def __init__(self, parent=None):
""" Initialize and setup the User Interface """
super(Program, self).__init__(parent)
self.setupUi(self)
""" Populate the Main Area """
self.mainArea.setHtml(self.intro_text())
""" Button Signal/Slots """
self.introButton.toggled.connect(self.intro_area)
self.runVulcanButton.clicked.connect(self.vulcan_run_area)
self.vulcanLogButton.clicked.connect(self.vulcan_log_area)
self.hostFileButton.clicked.connect(self.edit_host_area)
self.configEditButton.clicked.connect(self.edit_config_area)
def intro_text(self):
content_file = open("../content/intro_text.html").read()
return content_file
'''
Get the content to print
'''
def intro_area(self):
content_file = open("../content/intro_text.html").read()
self.mainArea.setHtml(content_file)
'''
Function that will display the data when the 'Run Vulcan' button is pressed
'''
def vulcan_run_area(self):
self.mainArea.setPlainText("Button Two ")
'''
Function that will display the data when the 'Vulcan Log' button is pressed
'''
def vulcan_log_area(self):
self.mainArea.setPlainText("Button Three")
'''
Function that will display the data when the 'Edit Host File' button is pressed
'''
def edit_host_area(self):
self.mainArea.setPlainText("Button Four")
'''
Function that will display the data when the 'Edit Config File' button is pressed
'''
def edit_config_area(self):
self.mainArea.setPlainText("Button Five")
#--------------------------------------------------------------------------
if __name__ == "__main__":
import sys
program = QtGui.QApplication(sys.argv)
mWindow = Program()
mWindow.show()
sys.exit(program.exec_())
I suggest you learn the basics of Qt to get acquainted with signals and slots.
You need to make the initially visible QPushButtons checkable (otherwise the 'revealed' buttons will only appear whilst the button is held down), and connect the toggled(bool) signal to the setVisible(bool) slot of the buttons you want to 'reveal'. Obviously for the buttons that are initially invisible, you would have to call setVisible(false) upon instantiation.
There are other, more reusable, ways of achieving the same effect - but this will get you started.
is there a way to create a signal that asserts when a combo box is opened and user uses the up - down arrows on the keyboard to select an item. So far the Qt4 reference lists signals that activate only after a mouse click or return key hit. I tried highlighted(int) and that only worked with another mouse click but when i use the up/down arrows, only the first item that was clicked is retrieved. I thought the current highlighted index is the one that is returned via self.ui.cb_dspBenchCmds.currentText().
here's a code snippet:
class CmdRef(Qg.QMainWindow):
def __init__(self,parent = None):
........
Qc.QObject.connect(self.ui.cb_dspBenchCmds, Qc.SIGNAL("activated(int)"), self.chooseCmd)
........
def chooseCmd(self):
whichCmd = self.ui.cb_dspBenchCmds.currentText()
cmdDescription = self.dictDspCmds[str(whichCmd)]
self.ui.te_dspBenchOutput.setText(''.join(cmdDescription))
thanks
dave
The highlighted signal does appear to be the one you want.
You just need to make use of the passed value:
class CmdRef(Qg.QMainWindow):
def __init__(self, parent = None):
...
self.ui.cb_dspBenchCmds.highlighted['QString'].connect(self.chooseCmd)
...
def chooseCmd(self, whichCmd):
cmdDescription = self.dictDspCmds[str(whichCmd)]
self.ui.te_dspBenchOutput.setText(''.join(cmdDescription))