how to properly destroy gtk.Dialog objects/widgets - python

Noob # programming with python and pygtk.
I'm creating an application which includes a couple of dialogs for user interaction.
#!usr/bin/env python
import gtk
info = gtk.MessageDialog(type=gtk.DIALOG_INFO, buttons=gtk.BUTTONS_OK)
info.set_property('title', 'Test info message')
info.set_property('text', 'Message to be displayed in the messagebox goes here')
if info.run() == gtk.RESPONSE_OK:
info.destroy()
This displays my message dialog, however, when you click on the 'OK' button presented in the dialog, nothing happens, the box just freezes.
What am I doing wrong here?

#mg
My bad. Your code is correct (and I guess my initial code was too)
The reason my dialog was remaining on the screen is because my gtk.main loop is running on a separate thread.
So all I had to was enclose your code (corrected version of mine) in between a
gtk.gdk.threads_enter()
and a
gtk.gdk.threads_leave()
and there it was.
Thanks for your response.

can you give me a last chance? ;)
there are some errors in your code:
you did not close a bracket
your syntax in .set_property is wrong: use: .set_property('property', 'value')
but i think they are copy/paste errors.
try this code, it works for me. maybe did you forget the gtk.main()?
import gtk
info = gtk.MessageDialog(buttons=gtk.BUTTONS_OK)
info.set_property('title', 'Test info message')
info.set_property('text', 'Message to be displayed in the messagebox goes here')
response = info.run()
if response == gtk.RESPONSE_OK:
print 'ok'
else:
print response
info.destroy()
gtk.main()

Related

How create local notification on MacOS Catalina pyobjc?

I am having some difficulty finding out how to send local notifications on Catalina using pyobjc.
The closes example I have seen is this:
PyObjC "Notifications are not allowed for this application"
Edit (June 27, 2020): I've created a package which has functionality to display notifications on Mac OS here. It will use PyObjC to create and display notifications. If It does not work for whatever reason, it will fallback to AppleScript notifications with osascript. I did some testing and found that the PyObjC notifications work on some devices but don't on some.
Answer:
I have also been searching for this answer, so I'd like to share what I've found:
The first thing you'll notice is that the function notify() defines a class, then returns an instance of it. You might be wondering why you can't directly call Notification.send(params). I tried it, but I was getting an error with the PyObjC, which I am unfortunately unable to fix:
# Error
class Notification(NSObject):
objc.BadPrototypeError: Objective-C expects 1 arguments, Python argument has 2 arguments for <unbound selector send of Notification at 0x10e410180>
Now onto the code:
# vscode may show the error: "No name '...' in module 'Foundation'; you can ignore it"
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject, NSDate
from PyObjCTools import AppHelper
def notify(
title='Notification',
subtitle=None, text=None,
delay=0,
action_button_title=None,
action_button_callback=None,
other_button_title=None,
other_button_callback=None,
reply_placeholder=None,
reply_callback=None
):
class Notification(NSObject):
def send(self):
notif = NSUserNotification.alloc().init()
if title is not None:
notif.setTitle_(title)
if subtitle is not None:
notif.setSubtitle_(subtitle)
if text is not None:
notif.setInformativeText_(text)
# notification buttons (main action button and other button)
if action_button_title:
notif.setActionButtonTitle_(action_button_title)
notif.set_showsButtons_(True)
if other_button_title:
notif.setOtherButtonTitle_(other_button_title)
notif.set_showsButtons_(True)
# reply button
if reply_callback:
notif.setHasReplyButton_(True)
if reply_placeholder:
notif.setResponsePlaceholder_(reply_placeholder)
NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
# setting delivery date as current date + delay (in seconds)
notif.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(delay, NSDate.date()))
# schedule the notification send
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notif)
# on if any of the callbacks are provided, start the event loop (this will keep the program from stopping)
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
def userNotificationCenter_didDeliverNotification_(self, center, notif):
print('delivered notification')
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
reply_callback(reply_text)
AppHelper.stopEventLoop()
# create the new notification
new_notif = Notification.alloc().init()
# return notification
return new_notif
def main():
n = notify(
title='Notification',
delay=0,
action_button_title='Action',
action_button_callback=lambda: print('Action'),
# other_button_title='Other',
# other_button_callback=lambda: print('Other'),
reply_placeholder='Enter your reply please',
reply_callback=lambda reply: print('Replied: ', reply),
)
n.send()
if __name__ == '__main__':
main()
Explanation
The notify() function takes in quite a few parameters (they are self-explanatory). The delay is how many seconds later the notification will appear. Note that if you set a delay that's longer than the execution of the program, the notification will be sent ever after the program is being executed.
You'll see the button parameters. There are three types of buttons:
Action button: the dominant action
Other button: the secondary action
Reply button: the button that opens a text field and takes a user input. This is commonly seen in messaging apps like iMessage.
All those if statements are setting the buttons appropriately and self explanatory. For instance, if the parameters for the other button are not provided, a Other button will not be shown.
One thing you'll notice is that if there are buttons, we are starting the console event loop:
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
This is a part of Python Objective-C. This is not a good explanation, but it basically keeps program "on" (I hope someone cane give a better explanation).
Basically, if you specify that you want a button, the program will continue to be "on" until AppHelper.stopEventLoop() (more about this later).
Now there are some "hook" functions:
userNotificationCenter_didDeliverNotification_(self, notification_center, notification): called when the notification is delivered
userNotificationCenter_didActivateNotification_(self, notification_center, notification): called when the user interacts with the notification (clicks, clicks action button, or reply) (documentation)
There surely are more, but I do not think there is a hook for the notification being dismissed or ignored, unfortunately.
With userNotificationCenter_didActivateNotification_, we can define some callbacks:
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
# action button callback
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
# reply button callback
reply_callback(reply_text)
AppHelper.stopEventLoop()
There are different activation types for the types of actions. The text from the reply action can also be retrieved as shown.
You'll also notice the AppHelper.stopEventLoop() at the end. This means to "end" the program from executing, since the notification has been dealt with by the user.
Now let's address all the problems with this solution.
Problems
The program will never stop if the user does not interact with the notification. The notification will slide away into the notification center and may or may never be interacted with. As I stated before, there's no hook for notification ignored or notification dismissed, so we cannot call AppHelper.stopEventLoop() at times like this.
Because AppHelper.stopEventLoop() is being run after interaction, it is not possible to send multiple notifications with callbacks, as the program will stop executing after the first notification is interacted with.
Although I can show the Other button (and give it text), I couldn't find a way to give it a callback. This is why I haven't addressed it in the above code block. I can give it text, but it's essentially a dummy button as it cannot do anything.
Should I still use this solution?
If you want notifications with callbacks, you probably should not, because of the problems I addressed.
If you only want to show notifications to alert the user on something, yes.
Other solutions
PYNC is a wrapper around terminal-notifier. However, both received their last commit in 2018. Alerter seems to be a successor to terminal-notifier, but there is not Python wrapper.
You can also try running applescript to send notifications, but you cannot set callbacks, nor can you change the icon.
I hope this answer has helped you. I am also trying to find out how to reliably send notifications with callbacks on Mac OS. I've figured out how to send notifications, but callbacks is the issue.

Pywinauto unable to find/close pop-up window

Source code
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
app = Application(backend='uia').start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
win = app['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.wait('ready')
win.menu_select("Setup->Refresh Serial and Ethernet")
win.top_window().print_control_identifiers(filename="file.txt")
# win.top_window().OKButton.click_input() ---------This is what I hope to do
else
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
Problem Statement
I had to run this application with elevation rights. The above is my code. The problem is I can't identify the window (view in output image) that pops up after selection from menu. I need to close the window. Please excuse the line
win.top_window().print_control_identifiers(filename="file.txt")
It was meant write the identifiers into a text file because the structure of this code does not display the outputs for me to view. However, since nothing is appended, I guess pywinauto couldn't identify the dialog.
For a clearer understanding, please view the image (input) of when it selects the menu.
Input
Now, it pops up with this dialog (output)
Output
I've also used spy to identify the caption and it gives:
(Handle: 004E07D4,
Caption: Information,
Class: #32770(Dialog),
Style: 94C801C5)
Other things I've tried:
Besides using win.topwindow() to identify the dialog, I've used
win[Information].OKButton.click_input()
win[Information].OK.click_input()
win[Information].OK.close()
win[Information].OK.kill(soft=false)
win.Information.OKButton.click_input()
win.Information.OK.click_input()
win.Information.OK.close()
win.Information.OK.kill(soft=false)
app[Information] ...... curious if I could discover the new window from original application
I've also send keys like enter, space, esc & alt-f4 to close the dialog with libraries like keyboard, pynput & ctypes. It still doesn't work.
Link to download the same application: http://downloadt.advantech.com/download/downloadsr.aspx?File_Id=1-1NHAMZX
Any help would be greatly appreciated !
I finally found a thread that demonstrated the way multi thread works to solve this issue. I tried it myself and it works. It's a little different as a few parts of the code have depreciated. Here is the link to the solution:
How to stop a warning dialog from halting execution of a Python program that's controlling it?
Here are the edits I made to solve the problem:
def is_admin():
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
if is_admin():
def __init__(self, window_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
def run(self):
while True:
try:
handles = windows.find_windows(title=self.window_name)
except windows.WindowNotFoundError:
pass
else:
for hwnd in handles:
app = Application()
app.connect(handle=hwnd)
popup = app[self.window_name]
popup.close()
if self.quit_event.is_set():
break
time.sleep(1)
quit_event = threading.Event()
mythread = ClearPopupThread('Information', quit_event)
mythread.start()
application = Application(backend="uia").start("C:\\Program Files (x86)\\Advantech\\AdamApax.NET Utility\\Program\\AdamNET.exe")
time.sleep(2)
win = application['Advantech Adam/Apax .NET Utility (Win32) Version 2.05.11 (B19)']
win.menu_select("Setup->Refresh Serial and Ethernet")
quit_event.set()
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1)
The best thing is this solution works for every other dialog that halts the main script from working & I could use them to do different actions like clicking buttons, inserting values, by adding more multi threads.

python:how to copy/replace text on internet textbox using hotkey

This has been bugging me for the last few days.
What I'm trying to achieve is:
1. Select(by dragging, shift, etc) text from an internet text editor.
2. press a hotkey (Alt-` in the code below)
3. Retrieve/copy that text to python program
4. Replace the selected text with a different block of text.
So if I select the word "stack" in a text editor and press Alt+`, then the selection should change into "stack overflow".
With the press of that hotkey only.
It seems pretty easy, but I found out that it really isn't.
This is as far as I've gotten:
import pyHook
import win32clipboard
import win32api, win32con
def sm():
handle = win32api.GetCurrentProcess()
win32api.SendMessage(handle ,win32con.WM_COPY, 0,0)
def OnKeyboardEvent(event):
if event.Alt != 0:
if event.KeyID != 192:
sm()
try:
win32clipboard.OpenClipboard()
a = win32clipboard.GetClipboardData(13)
finally:
try:
win32clipboard.CloseClipboard()
finally:
print "error"
return True
hm = pyHook.HookManager()
hm.KeyDown = OnKeyboardEvent
hm.HookKeyboard()
if __name__ == '__main__':
import pythoncom
pythoncom.PumpMessages()
This is just one of the many codes I've tried.
I'm not even sure if WM_COPY is the right message to use. In fact, I don't really get what messages are supposed to do and how they work.
Retrieving the text is just the first part of the whole program and I'm stuck.
I don't care if I use the clipboard to make this happen.
Any ideas? I really want to make this work, because I do this tedious replacement every day.

How to get window application status in python

I'm currently writing a piece of code to test windows app based on pyautowin.
When of the test is to check if we can minimized the window.
Below is the code:
MyApp.Start_(bittorrentApp)
time.sleep(2)
w_handle = pywinauto.findwindows.find_windows(title=u'Bittorrent Automation Task', class_name='WindowsForms10.Window.8.app.0.2bf8098_r15_ad1')[0]
window = MyApp.window_(handle=w_handle)
window.Click()
window.ClickInput(coords = (300,10))
time.sleep(1)
lStyles = win32api.GetWindowLong(GWL_STYLE);
if( lStyles & WS_MINIMIZE ):
print "minimized"
else:
print "not minimized"
I have imported win32api and I can minimized the window.
By the way
lStyles = win32api.GetWindowLong(GWL_STYLE);
return an error, saying GWL_STYLE is not defined
Any idea ?
pywinauto already has all such functionality.
if window.HasStyle(pywinauto.win32defines.WS_MINIMIZE):
window.Minimize()
That's all in HwndWrapper class. You can see all its attributes when typing window.WrapperObject(). in popup hint. WrapperObject() call is usually hidden for readability, but it's called implicitly anyway.
BTW, GetWindowLong(handle, style) has 2 parameters.

How can I get the ProgressDialog to close before I open my MessageBox?

I've made a runnable example that demonstrates the buggy behavior: http://pastebin.com/8KpzD4pw
This issue is EXTREMELY aggravating. I have a wx.ProgressDialog up while I'm saving a file, and upon IOError, I want to close the progress dialog and display an error message. Unfortunately, this seems to be impossible. The progress dialog blatantly refuses to close before the message box closes:
As you can see, the message box appears below the progress dialog, so the user has to manually switch focus to the message box to see it's contents. When the message box is closed, the progress dialog disappears as well. Here is the code for the save function:
def save(self, path=None):
# Show a loading dialog while the document is staving.
progress = shared.show_loading(self, 'Saving document')
try:
raise IOError('Error message')
if not path:
self.document.save()
else:
self.document.save_to_file(path)
except IOError as e:
progress.done()
message = 'Failed to save file:\n\n{}'.format(e.message)
wx.MessageBox(message, 'Error', wx.OK | wx.ICON_ERROR)
progress.done()
The show_loading and progress.done functions are just shortcuts for using the wx.ProgressDialog (source).
Why does the progress dialog not disappear before the message box is opened? How can I fix it?
I have also tried using wx.CallAfter to open the message box, to no avail:
# ...
except IOError as e:
message = 'Failed to save file:\n\n{}'.format(e.message)
def show_error():
wx.MessageBox(message, 'Error', wx.OK | wx.ICON_ERROR)
progress.done()
wx.CallAfter(show_error)
# ...
I have also tried to sleep for 100ms between closing the progress dialog and opening the message box using wx.MicroSleep without success.
I have also tried calling wx.Yield() and wx.WakeUpIdle() right after destroying the progress dialog, neither having any effect.
Just out of curiosity... Have you tried using wx.SafeYield() or wx.Yield() or wx.YieldIfNeeded() right after the call to progressdialog.Destroy()?
Your sample is not runnable as it stands so I am just shooting in the dark.
I think Infinity77 has the right answer here. People forget that GUI calls are not synchronous -- they aren't finished by the time they return. That "done" call sends a message to the window, and in response to that, the window probably queues up several more messages to clean itself up. When you fire up a model message box, that creates its OWN message loop, while leaving the original message loop in suspended animation. Thus, the cleanup messages cannot be processed until the message box returns and your main message loop runs again. A Yield call will allow those queued up messages to drain.
I had a similar case, which I finally resolved by calling:
dlg.Update( dlg.GetRange( ) )
It seems that, at least when you put the progress dialog into "pulse" mode, it won't immediately respond to Destroy calls. No amount of sleeping or yielding before or after destroying it would convince my progress dialog to stop displaying. However, by instead simply updating the value to the max, it seems to automatically destroy (or at least hide) itself immediately.
The wxPython demo shows how to interrupt a ProgressDialog. It also shows that you need to Destroy() it instead of Close() it, which is the normal way of getting rid of dialogs. In your exception handler, you will want to stop whatever the ProgressDialog is keeping track of and Destroy() it. Then show your MessageBox.
I have figured out a workaround. It turns out that I can't remove the native windows progress dialog right after I create it. I have to wait a while, probably for the dialog to be completely initialized, before I'm allowed to destroy it. I added this code:
wx.MilliSleep(50)
Into my progress dialog shortcut function, which introduces an unnoticeable delay after opening the progress dialog and allows me to destroy the progress dialog when ever I want.
Complete shortcut function:
def show_loading(parent, title, message=None, maximum=100):
if not message:
message = title
# A class for the return value.
class LoadingObject(object):
def __init__(self, dialog):
self.dialog = dialog
self.is_done = False
def done(self):
if not self.is_done:
self.dialog.Destroy()
self.is_done = True
def pulse(self, message):
self.dialog.Pulse(message)
def progress(self, current, message=None):
# Don't allow the progress to reach 100%, since it will freeze the
# dialog.
if current >= maximum:
current = current - 1
if message is not None:
self.dialog.Update(current, message)
else:
self.dialog.Update(current)
# Create the progress dialog.
dlg_style = wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME
dlg = wx.ProgressDialog(
title, message, parent=parent, style=dlg_style, maximum=maximum
)
dlg.Pulse()
# Wait just a little bit to allow the progress dialog to initialize.
wx.MilliSleep(50)
# Return an instance of the LoadingDialog with the progress dialog attached.
return LoadingObject(dlg)
Final save function:
def save(self, path=None):
# Show a loading dialog while the document is staving.
progress = shared.show_loading(self, 'Saving document')
try:
if not path:
self.document.save()
else:
self.document.save_to_file(path)
except IOError as e:
message = 'Failed to save file:\n\n{}'.format(e.message)
wx.MessageBox(message, 'Error', wx.OK | wx.ICON_ERROR)
finally:
progress.done()
I took a slightly different approach to this problem. Had multiple function calls in a linear order, and wanted to show the overall progress as function calls were done. I simply wrapped the progress bar in a function, taking the function to call and its arguments. On exception I destroy the progress bar, and raise the exception. Example below:
def _progress_wrap(self, func, *args, **kwargs):
self.count += 1
self.progress.Update(self.count)
res = None
try:
res = func(*args, **kwargs)
except Exception:
self.progress.Destroy()
raise
return(res)

Categories

Resources