WxPython - Mouse Click Not getting recognised on the GUI - python

I am new to python. I am trying to gather links from youtube based on search string. Assumption is the link I want will be the first result on my search results.
Problem I am facing is mouse clicks are not recognised on the gui. It just moves to the x,y cordinates but I cannot see that it is getting clicked. Please help me to understand why this clicks are not recognized.
Below is the code: I created a GUI in wxpython, and I am trying to click on search bar of youtube, then paste the search string, then click on search take the link address from the first result. This below code is sample code. In my real code search string will be passed from excel sheet.
import win32api, win32con
import win32com.client as win32
from win32com.client import Dispatch, Constants
import os
import time
import win32clipboard
import wx
import wx.html2
import sys
x_pad = 0
y_pad = 0
def leftClick():
wx.EVT_LEFT_DOWN
time.sleep(.1)
wx.EVT_LEFT_UP
print "Click." #completely optional. But nice for debugging purposes.
def rightClick():
wx.EVT_RIGHT_DOWN
time.sleep(.1)
wx.EVT_RIGHT_UP
print "Right Click." #completely optional. But nice for debugging purposes.
def mousePos(cord):
win32api.SetCursorPos((x_pad + cord[0], y_pad + cord[1]))
class URL(wx.Frame):
def __init__(self, *args, **kw):
super(URL, self).__init__(*args, **kw)
self.InitUI()
def InitUI(self):
pnl = wx.Panel(self)
sbtn = wx.Button(pnl, label='Start', pos=(850, 560))
cbtn = wx.Button(pnl, label='Close', pos=(850, 610))
stdot = wx.TextCtrl(pnl, pos =(400,580),size=(400,80), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
sys.stdout = stdot
sys.stderr = stdot
sbtn.Bind(wx.EVT_BUTTON, self.OnStart)
cbtn.Bind(wx.EVT_BUTTON, self.OnClose)
brwser = wx.html2.WebView.New(pnl, size = (1280,550), pos = (0,0), url =("http://www.youtube.com/results?search_query=sarabhai+vs+sarabhai+episode+25&sm=3"))
self.SetSize((1280, 720))
self.SetTitle('YouTube URL Grab')
self.SetPosition((0,0))
self.Show(True)
def OnStart(self,e):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText("Sarabhai vs Sarabhai episode 31") #Sample string for search
win32clipboard.CloseClipboard()
mousePos((252, 53))
leftClick()
leftClick()
leftClick()
rightClick()
time.sleep(5)
mousePos((280, 144))
leftClick()
time.sleep(5)
mousePos((755, 53))
leftClick()
time.sleep(7)
mousePos((467, 140))
rightClick()
time.sleep(5)
mousePos((514, 321))
leftClick()
def OnClose(self, e):
self.Close(True)
def main():
ex = wx.App()
URL(None)
ex.MainLoop()
if __name__ == '__main__':
main()

The names wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP are actually just numbers for the event identifies that you need to bind to they do not cause the events to happen! Look at the examples in the docs and demos package.
The basic process is:
During frame intitialisation bind handlers to the events you need to handle, these should be of type fn(self, evt) and by convention are named onNameOfEvent e.g. def onLeftBtnDown(self, evt) these contain the events that you need to react to and the evt parameter gives you an event object the members of which depend on the event type. The event binding uses the format self.Bind(wx.EVT_NAME_OF_EVENT, onNameOfEvent) and is normally in the windows __init__ method, (this is what the vast majority of GUI code does, including non wx GUIs).
If, (much more rarely), you need to generate events from your code rather than just reacting to the events you still need to do the above then where you need to create the event by declaring the appropriate type of event object, e.g. wx.CommandEvent, (note that you can create your own event types), populating with the required values and then use 'wx.PostEvent' to add it to the event queue for the window or control that it applies to. Sometime later, when it gets to the top of the queue, your event handler will be called with this event.

Related

How to display a PyQt5/PySide2 dialog box while other processes run in the background

Overview: I have a fairly large GUI program, built in PyQt5/PySide2 that performs a multitude of operations. Some processes are very quick and a few can take up to around a minute to complete. I had my program set up to display a sort of 'Please Wait...' dialog box for any process that took more than a second or so. I did this using the threading module in conjunction with the signals built into PyQt5/PySide2. While it worked fine, I found that I could not also run threading using concurrent.futures, because these threading modules did not function well together. concurrent.futures did not handle the signals and the threading was generally much slower when processing a multitude of functions back-to-back. So I take my pick when it is appropriate to use either one.
Problem: However, I started experiencing instances where, though the dialog box would appear, it would not display the text within the box, which usually was a message along the lines of "PLEASE WAIT, PROCESSING REQUEST". Essentially, the process of displaying the text was/is being held up until the underlying process finishes. After the process finishes, as long as the window isn't closed, it would then show the text.
Obstacle: In addition to the above described situation, I have re-written parts of the signal and dialog display classes, which on the surface, appear to function as expected and I have included the full code of a basic example. However, when I apply those exact methods to my larger program, it closes itself when it first begins to display the dialog box.
Question: I am wondering if I am missing a basic element or concept in this below example, which when applied to a much bigger program, possibly creates some issues for me. I am looking for the potential red flags in this example.
EDIT: When you run this example, click on the OK button to test the dialog box and threading. The 'main' box will disappear and only the dialog box should be displayed. After the 3 seconds are up, the dialog box disappears and another box appears. This closely resembles the actual functionality of my larger program. Essentially, when you start up you 'login' to the program, so the start menu disappears and then the actual program initializes and loads up. As you can see with this example, the box will display briefly then disappears and this is what happens in my program. A user logs in, but then within a second of logging in, the program closes. I have tried variations on how to get the window to load. The one listed below actually displays it at least, but other methods I've used will just result in a QApplication::exec: Must be called from the main thread error. I've tried a few other methods and listed them below, though obviously none of them work.
import sys
from PySide2 import QtWidgets, QtCore
import PySide2
import time
import threading
def startThread(functionName, *args, **kwargs):
startThread.t = threading.Thread(target=functionName)
startThread.t.daemon = True
startThread.t.start()
class UserInput(object):
def setupUi(self, get_user_input=None):
# Basic shape
self.width = 175
get_user_input.setObjectName("get_user_input")
get_user_input.resize(175, self.width)
# Grid layout for the buttons
self.buttonLayoutGrid = QtWidgets.QWidget(get_user_input)
self.buttonLayoutGrid.setGeometry(QtCore.QRect(10, 115, 155, 50))
self.buttonLayoutGrid.setObjectName("buttonLayoutGrid")
self.buttonLayout = QtWidgets.QGridLayout(self.buttonLayoutGrid)
self.buttonLayout.setContentsMargins(0, 0, 0, 0)
self.buttonLayout.setObjectName("buttonLayout")
self.buttonLayout.setAlignment(PySide2.QtCore.Qt.AlignLeft|PySide2.QtCore.Qt.AlignVCenter)
# Buttons
self.buttonOK = QtWidgets.QPushButton(self.buttonLayoutGrid)
self.buttonOK.setObjectName("buttonOK")
self.buttonOK.setText("OK")
class FakeBox(PySide2.QtWidgets.QDialog):
def __init__(self):
super(FakeBox, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
def setupUi(self, box_details):
box_details.setObjectName("box_details")
box_details.resize(500, 89)
self.labelProcessStatus = QtWidgets.QLabel(box_details)
self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
self.labelProcessStatus.setWordWrap(True)
self.labelProcessStatus.setObjectName("labelProcessStatus")
self.buttonProcessCompleted = QtWidgets.QPushButton(box_details)
self.buttonProcessCompleted.setEnabled(False)
self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
QtCore.QMetaObject.connectSlotsByName(box_details)
class FUNCTION_RUN(PySide2.QtWidgets.QDialog):
display_dialog_window = PySide2.QtCore.Signal(str)
display_process_complete = PySide2.QtCore.Signal(str)
process_complete_no_msg = PySide2.QtCore.Signal()
def __init__(self):
super(FUNCTION_RUN, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
def setupUi(self, functionRunning):
functionRunning.setObjectName("functionRunning")
functionRunning.resize(234, 89)
self.labelProcessStatus = QtWidgets.QLabel(functionRunning)
self.labelProcessStatus.setGeometry(QtCore.QRect(10, 0, 221, 51))
self.labelProcessStatus.setAlignment(QtCore.Qt.AlignCenter)
self.labelProcessStatus.setWordWrap(True)
self.labelProcessStatus.setObjectName("labelProcessStatus")
self.buttonProcessCompleted = QtWidgets.QPushButton(functionRunning)
self.buttonProcessCompleted.setEnabled(False)
self.buttonProcessCompleted.setGeometry(QtCore.QRect(60, 60, 111, 23))
self.buttonProcessCompleted.setObjectName("buttonProcessCompleted")
QtCore.QMetaObject.connectSlotsByName(functionRunning)
def show_msg(self, msg_text=None):
self.setWindowTitle("RUNNING")
self.labelProcessStatus.setText(msg_text)
self.setModal(False)
self.show()
def process_complete(self, msg_text=None):
self.setWindowTitle("FINISHED")
self.labelProcessStatus.setText(msg_text)
self.buttonProcessCompleted.setText('OK')
self.buttonProcessCompleted.setEnabled(True)
self.setModal(False)
self.show()
def process_finished(self):
self.hide()
class UserInputPrompt(PySide2.QtWidgets.QDialog, UserInput):
def __init__(self):
super(UserInputPrompt, self).__init__()
self.setupUi(self)
self.buttonOK.clicked.connect(self.scoreMass)
def some_more_text(self):
print('Some more...')
def scoreMass(self):
startThread(MASTER.UI.display_msg)
def display_msg(self):
def dialog():
MASTER.UI.hide()
m = ' Processing things...'
MASTER.processing_window.display_dialog_window.emit(m)
MASTER.UI.some_more_text()
time.sleep(3)
MASTER.Second_UI.show()
MASTER.processing_window.process_complete_no_msg.emit()
dialog()
class MASTER(object):
def __init__(self):
super(MASTER, self).__init__()
MASTER.UI = UserInputPrompt()
MASTER.Second_UI = FakeBox()
MASTER.processing_window = FUNCTION_RUN()
MASTER.processing_window.display_dialog_window.connect(MASTER.processing_window.show_msg)
MASTER.processing_window.display_process_complete.connect(MASTER.processing_window.process_complete)
MASTER.processing_window.process_complete_no_msg.connect(MASTER.processing_window.process_finished)
MASTER.UI.show()
app.exec_()
def main():
MASTER()
if __name__ == '__main__':
global app
app = PySide2.QtWidgets.QApplication(sys.argv)
main()
The line where you have MASTER.Second_UI.show() Is probably where you're getting held up. You created an instance in your main thread, which is good, but you'll need to create a signal in that class that you can emit the show() method. Make the FakeBox class look like this:
class FakeBox(PySide2.QtWidgets.QDialog):
show_new_prompt = PySide2.QtCore.Signal()
def __init__(self):
super(FakeBox, self).__init__()
self.setupUi(self)
self.buttonProcessCompleted.clicked.connect(self.close)
And then in your MASTER class look like this:
class MASTER(object):
def __init__(self):
super(MASTER, self).__init__()
MASTER.UI = UserInputPrompt()
MASTER.Second_UI = FakeBox()
MASTER.Second_UI.show_new_prompt.connect(MASTER.Second_UI.show)
# Keeping everything after this line
And then lastly, in your display_msg() function, change it to this:
def display_msg(self):
def dialog():
MASTER.UI.hide()
m = ' Processing things...'
MASTER.processing_window.display_dialog_window.emit(m)
MASTER.UI.some_more_text()
time.sleep(3)
MASTER.processing_window.process_complete_no_msg.emit()
MASTER.Second_UI.show_new_prompt.emit()
dialog()
This should follow the progression as you described and will keep the last window displayed at the end.

Providing right-click context menu for a separate program using wxPython

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()

Disable keyboard but keep getting events

I have a touchscreen laptop that folds back enough to become like a tablet. If I put it down on the table, I don't want to be hitting keys accidentally, so I'm working on a script to disable the keyboard when I hit Ctrl-F10 and then re-enable it when I do that again. I'm using xlib from PyPI, and I've gotten this so far:
from Xlib.display import Display
from Xlib.ext import xinput
class Handler:
def __init__(self, display):
self.enabled = True
self.display = display
def handle(self, event):
if event.data['detail'] == 76 and event.data['mods']['base_mods'] == 4:
if self.enabled:
self.display.grab_server()
else:
self.display.ungrab_server()
self.enabled = not self.enabled
try:
display = Display()
handler = Handler(display)
screen = display.screen()
screen.root.xinput_select_events([
(xinput.AllDevices, xinput.KeyPressMask),
])
while True:
event = display.next_event()
handler.handle(event)
finally:
display.close()
It does disable the keyboard on Ctrl-F10, but as soon as I re-enable, all the keys I pressed when it was disabled are activated all at once. Is there a way to clear the queue before re-enabling, or a better way to disable the keyboard?
Try XGrabKeyboard: https://tronche.com/gui/x/xlib/input/XGrabKeyboard.html
(But this requires you to create your own window for grabbing; you can e.g. create a window of size 1x1 at position -10x-10)
I think the values for things like owner_events and keyboard_mode do not matter much. The main effect should be that the input focus goes to your own window. time should be CurrentTime (which is 0) and pointer_mode should be GrabModeAsync, so that you do not interfere with the pointer.

binding packages with wxpython

I'm kind of new with wxpython and python itself, sorry for the basic question.
I'm trying to organize my code in a more easy way to manage it. I created this simple example that kind of resume my problem. Basically it is just a window with a button that print a message. I separated it in three simple packages
ef_Main.py - This is the main packages it will import the UI and the application itself.
ef_Tool.py - it is the application that will run all the important code, right now it is just a print statment but will have all the application code.
ef_UI.py - a very basic interface using wxpython.
How it should work:
Run ef_Main.py it will import the interface (ef_UI.py) and the main code (ef_Tool.py). When something is click in the interface it will be ready by the ef_Main and send to ef_Tool to be executed.
My problem is:
I'm not sure how to use bind function to connect this three packages. I believe that it should be in the ef_Main but how it will get the info from the interface and send it to the ef_Tool.py.
And if I want to get some output from the ef_Tool and send it back to the interface. How should I do that.
Here is my code.
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
''' Here is where I don't know how to make it works,
if I should put a class here or not, and how to bind
this function with the impTool and impUI'''
#class MyMain(self):
def uiControls(self):
self.Bind(wx.EVT_BUTTON, eU.OnClick(self), eT.MyTools(self))
def main():
app = wx.App(False)
frame = eU.MyFrame()
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
=======================
#ef_Tool.py
import wx
'''just a simple as possible function to be execute when it is called '''
class MyTools():
def OnClick(self, event):
#Value = self.MyTextCtrl.GetValue()
print "it is working! "
=======================
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
Thanks in advance!
Emerson
Here is a simple example of fulfilling your requirements. Supposing all your application logic is done in ef_Tool.py and the input for these logic is from ef_UI and the output is also sent to ef_UI.py.
You need to call a method in ef_Tool.py when a button click event occurs in ef_UI. You can invoke this method from MyFrame's method. But you need an obj of MyTools to do this.
so first, create an obj for MyTools in ef_Main.py and pass this object to MyFrame
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
def main():
efToolObj = eT.MyTools() # object of MyTools class
app = wx.App(False)
frame = eU.MyFrame(efToolObj) # Pass this to MyFrame so that it can make use of it
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
Store this MyTools's object in you MyFrame class. And then use this object to call the corresponding method inside MyTools
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self, efToolObj):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.efToolObj = efToolObj # save the MyTools object to be used later
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyButton.Bind(wx.EVT_BUTTON, self.onClickEvent) # Bind the click event to an event handling method
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
def onClickEvent(self,event): #this is called when a button is clicked
res = self.efToolObj.OnClickPrinting(self.MyTextCtrl.GetValue()) #Use the mytools object to call its method to apply logic,also get result values
self.MyTextCtrl.SetValue(res) #use the result values in your UI
You can pass the info you want to send to the application logic in its arguments and get results as return values.
#ef_Tool.py
class MyTools:
def OnClickPrinting(self,textvalue):
#Value = self.MyTextCtrl.GetValue()
print "it is working! ",textvalue
resultstr = "test successful"
return resultstr
Hope this helped.

wxPython popup from calling imported function

I have a GUI made with wxPython that calls a function that I imported from a separate Python file when I press a button, and shows the output of that function in a text box. I want to improve it so that if the function asks for user input mid-execution (like a raw_input()), I want a new popup window to appear instead of the raw_input waiting in the text box. I've been looking through the wxPython documentation but can't seem to find anything that resembles what I want, so I was wondering if anyone here could give me any pointers.
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
cont = raw_input("Something has gone wrong. Do you still want to continue?")
if(cont.lower() == "n")
sys.exit(0)
#more function code...
print "Success!"
I would call the external function via a button event. Instead of raw_input, I would just use a wx.MessageDialog with a yes or no button on it. You can check which button the user pressed and continue or not accordingly. Here are some links on that dialog and others:
http://wxpython.org/Phoenix/docs/html/MessageDialog.html
http://www.blog.pythonlibrary.org/2010/06/26/the-dialogs-of-wxpython-part-1-of-2/
http://zetcode.com/wxpython/dialogs/
If this piece of code you are running takes a long time (i.e. greater than a second), then it is probably going to block wx's mainloop and cause the application to become unresponsive. If that is the case, then you'll need to move this code into a thread. The following articles will help you with that course of action:
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/

Categories