Currently working through a free online class for Python from Cybrary (I'm coding in 3.6), but I use a Mac while the presenter uses Windows. So far, there have been very few differences if any.
The current section deals with learning and using Ctypes however, and the "assignment" says to Write a function which takes two arguments, title and body and creates a MessageBox with those arguments.
The code used in the video as an example of creating a Message Box:
from ctypes import *
windll.user32.MessageBoxA(0, "Click Yes or No\n", "This is a title\n", 4)
My code:
# 2.1 Ctypes: Write a function which takes two arguments, title and body
# and creates a MessageBox with those arguments
def python_message_box(title, body):
return windll.user32.MessageBoxA(0, body, title, 0)
Running this gives the error:
File ".../AdvancedActivities.py", line 9, in python_message_box
return windll.user32.MessageBoxA(0, body, title, 0)
NameError: name 'windll' is not defined
I don't believe I need to say that I get the same error trying to run
windll.user32.MessageBoxW(0, body, title, 0)
I haven't been able to find any examples anywhere of people creating Message Boxes on Mac computers. Is it a Windows-specific function? If so, what would be the Mac equivalent of this?
EDIT: Mark Setchell's solution is to have Python run terminal functions that accomplish windll tasks, so instead of windll.user32.MessageBoxA(0, body, title, 0), use:
command = "osascript -e 'Tell application \"System Events\" to
display dialog \""+body+"\"'"
system(command)
If you type this into a Terminal on any Mac, you'll get a dialog box:
osascript -e 'Tell application "System Events" to display dialog "Some Funky Message" with title "Hello Matey"'
See here for further examples.
So, just use a Python subprocess call to run that... subprocess documentation, or use system().
Nothing to install. No dependencies. You can also ask user for values, select files or directories and pick colours using the same technique. The dialog boxes are all native Mac ones - not some ugly imitation.
import os
body_Str="Body of Dialog"
title_Str="Title"
os.system("""osascript -e \'Tell application \"System Events\" to display dialog \""+body_Str+"\" with title \""+title_Str+"\"\'""")
this is much better
Related
I have a python script I made a couple of months ago that is command line based. I want to make an optional GUI for it. It is a fairly basic program, it fetches cue files for game roms. I coded the GUI separately, and it came to mind that instead of trying to implement it into the code of the program, it'd be 10 times easier to just execute the program with the flags the user specified on the GUI, then print the output on a text field. This is the GUI:
The program parses flags with the argparse library. It has a positional argument which is the directory, and two optional flags being -r and -g (I guess you can identify which is which). How could I do it?
You can use subprocess.check_output() to get the output of a subprocess. Here's sample usage:
var = subprocess.check_output(
['python3', 'CueMaker.py'], shell=True, stderr=subprocess.STDOUT
)
# redirects the standrad error buffer to standrad output to store that in the variable.`
Then I can use var to change the text, assuming there's a StringVar() tied to the widget:
stringVar.set(var)
I need to create a message box in python without using python Tkinter library so that I can use that before using exit() function this will display the message and answer as soon as user presses okay, user gets out of program.
Here's one way to do it with Windows' msg command. The code is based on #ErykSun's comment under the question Can't execute msg (and other) Windows commands via subprocess.
import os
import subprocess
sysroot = os.environ['SystemRoot']
sysnative = (os.path.join(sysroot, 'SysNative')
if os.path.exists(os.path.join(sysroot, 'SysNative'))
else
os.path.join(sysroot, 'System32'))
msgexe_path = os.path.join(sysnative, 'msg.exe')
subprocess.run([msgexe_path, '*', 'ALL YOUR BASE ARE WHERE BELONG TO US.'])
Introduction
In order to group several instances of a given application under one icon in the desktop launcher (I am using Ubuntu 17.04) they must have the same appName property of the WM_CLASS string. For example, if I run emacs twice:
$ emacs &
$ emacs &
Both instances will show up under the Emacs icon in the desktop launchbar. The reason is that both instances have the same WM_CLASS string. We can check this string using
$ xprop WM_CLASS
and then click on the Emacs window. It then shows:
WM_CLASS(STRING) = "emacs", "Emacs"
Here "emacs" is the resource (appName), and "Emacs" is the className,
see xdotool: what are “class” and “classname” for a window? for more information.
Question
Consider this program (my-tkapp.py):
#! /usr/bin/env python
import tkinter as tk
root = tk.Tk(className='myTkApp')
label = tk.Label(root, text="Hello World")
label.pack()
root.mainloop()
If I run this program twice:
$ my-tkapp.py &
$ my-tkapp.py &
and then run xprop to check the WM_CLASS property of both windows,
the first window gives:
WM_CLASS(STRING) = "myTkApp", "Mytkapp"
whereas the second gives:
WM_CLASS(STRING) = "myTkApp #2", "Mytkapp"
Note that tkinter has added a #2 suffix to the app name property. This is not desired. It makes the window manager group the two windows under separate icons in the desktop launch bar.
How can I keep the same appName property of the WM_CLASS string for different instances of my application?
See also
How to add launcher icon for python script?
This is tkinters behavior by default. The className must be unique in order to work with tkinter.send and is documented as appname.
If newName is not specified, this command returns the name of the
application (the name that may be used in send commands to communicate
with the application). If newName is specified, then the name of the
application is changed to newName. If the given name is already in
use, then a suffix of the form “ #2” or “ #3” is appended in order to
make the name unique. The command's result is the name actually
chosen. newName should not start with a capital letter. This will
interfere with option processing, since names starting with capitals
are assumed to be classes; as a result, Tk may not be able to find
some options for the application. If sends have been disabled by
deleting the send command, this command will reenable them and
recreate the send command.
Use wm_group instead. Also see this Q&A for dialog windows
While the first string does add the suffix, if you use the second string the "Mytkapp" in your configuration, it will apply to all versions of the app that are running.
I have a simple script which parses a file and loads it's contents to a database. I don't need a UI, but right now I'm prompting the user for the file to parse using raw_input which is most unfriendly, especially because the user can't copy/paste the path. I would like a quick and easy way to present a file selection dialog to the user, they can select the file, and then it's loaded to the database. (In my use case, if they happened to chose the wrong file, it would fail parsing, and wouldn't be a problem even if it was loaded to the database.)
import tkFileDialog
file_path_string = tkFileDialog.askopenfilename()
This code is close to what I want, but it leaves an annoying empty frame open (which isn't able to be closed, probably because I haven't registered a close event handler).
I don't have to use tkInter, but since it's in the Python standard library it's a good candidate for quickest and easiest solution.
Whats a quick and easy way to prompt for a file or filename in a script without any other UI?
Tkinter is the easiest way if you don't want to have any other dependencies.
To show only the dialog without any other GUI elements, you have to hide the root window using the withdraw method:
import tkinter as tk
from tkinter import filedialog
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename()
Python 2 variant:
import Tkinter, tkFileDialog
root = Tkinter.Tk()
root.withdraw()
file_path = tkFileDialog.askopenfilename()
You can use easygui:
import easygui
path = easygui.fileopenbox()
To install easygui, you can use pip:
pip3 install easygui
It is a single pure Python module (easygui.py) that uses tkinter.
Try with wxPython:
import wx
def get_path(wildcard):
app = wx.App(None)
style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST
dialog = wx.FileDialog(None, 'Open', wildcard=wildcard, style=style)
if dialog.ShowModal() == wx.ID_OK:
path = dialog.GetPath()
else:
path = None
dialog.Destroy()
return path
print get_path('*.txt')
pywin32 provides access to the GetOpenFileName win32 function. From the example
import win32gui, win32con, os
filter='Python Scripts\0*.py;*.pyw;*.pys\0Text files\0*.txt\0'
customfilter='Other file types\0*.*\0'
fname, customfilter, flags=win32gui.GetOpenFileNameW(
InitialDir=os.environ['temp'],
Flags=win32con.OFN_ALLOWMULTISELECT|win32con.OFN_EXPLORER,
File='somefilename', DefExt='py',
Title='GetOpenFileNameW',
Filter=filter,
CustomFilter=customfilter,
FilterIndex=0)
print 'open file names:', repr(fname)
print 'filter used:', repr(customfilter)
print 'Flags:', flags
for k,v in win32con.__dict__.items():
if k.startswith('OFN_') and flags & v:
print '\t'+k
Using tkinter (python 2) or Tkinter (python 3) it's indeed possible to display file open dialog (See other answers here). Please notice however that user interface of that dialog is outdated and does not corresponds to newer file open dialogs available in Windows 10.
Moreover - if you're looking on way to embedd python support into your own application - you will find out soon that tkinter library is not open source code and even more - it is commercial library.
(For example search for "activetcl pricing" will lead you to this web page: https://reviews.financesonline.com/p/activetcl/)
So tkinter library will cost money for any application wanting to embedd python.
I by myself managed to find pythonnet library:
Overview here: http://pythonnet.github.io/
Source code here: https://github.com/pythonnet/pythonnet
(MIT License)
Using following command it's possible to install pythonnet:
pip3 install pythonnet
And here you can find out working example for using open file dialog:
https://stackoverflow.com/a/50446803/2338477
Let me copy an example also here:
import sys
import ctypes
co_initialize = ctypes.windll.ole32.CoInitialize
# Force STA mode
co_initialize(None)
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import OpenFileDialog
file_dialog = OpenFileDialog()
ret = file_dialog.ShowDialog()
if ret != 1:
print("Cancelled")
sys.exit()
print(file_dialog.FileName)
If you also miss more complex user interface - see Demo folder
in pythonnet git.
I'm not sure about portability to other OS's, haven't tried, but .net 5 is planned to be ported to multiple OS's (Search ".net 5 platforms", https://devblogs.microsoft.com/dotnet/introducing-net-5/ ) - so this technology is also future proof.
If you don't need the UI or expect the program to run in a CLI, you could parse the filepath as an argument. This would allow you to use the autocomplete feature of your CLI to quickly find the file you need.
This would probably only be handy if the script is non-interactive besides the filepath input.
Another os-agnostic option, use pywebview:
import webview
def webview_file_dialog():
file = None
def open_file_dialog(w):
nonlocal file
try:
file = w.create_file_dialog(webview.OPEN_DIALOG)[0]
except TypeError:
pass # user exited file dialog without picking
finally:
w.destroy()
window = webview.create_window("", hidden=True)
webview.start(open_file_dialog, window)
# file will either be a string or None
return file
print(webview_file_dialog())
Environment: python3.8.6 on Mac - though I've used pywebview on windows 10 before.
I just stumbled on this little trick for Windows only: run powershell.exe from subprocess.
import subprocess
sys_const = ssfDESKTOP # Starts at the top level
# sys_const = 0x2a # Correct value for "Program Files (0x86)" folder
powershell_browse = "(new-object -COM 'Shell.Application')."
powershell_browse += "BrowseForFolder(0,'window title here',0,sys_const).self.path"
ret = subprocess.run(["powershell.exe",powershell_browse], stdout=subprocess.PIPE)
print(ret.stdout.decode())
Note the optional use of system folder constants. (There's an obscure typo in shldisp.h that the "Program Files (0x86)" constant was assigned wrong. I added a comment with the correct value. Took me a bit to figure that one out.)
More info below:
System folder constants
I've recently started learning Python and wrote a little script that informs me when a certain website changes content. I then added it as a scheduled task to Windows so it can run every 10 minutes. I'd like to be informed of the website changing right away so I added a win32ui MessageBox that pops up if the script detects that the website has changed. Here's the little code snippet I'm using for the MessageBox (imaginative text, I know):
win32ui.MessageBox("The website has changed.", "Website Change", 0)
My issue is this, I spend most of my time using remote desktop so when the MessageBox does pop up it sits behind the remote desktop session, is there any way to force the MessageBox to appear on top of it?
On a similar note when the script runs the command line opens up very briefly over the remote desktop session which I don't want, is there any way of stopping this behaviour?
I'm happy with Windows specific solutions as I'm aware it might mean dealing with the windowing manager or possibly an alternative way to inform me rather than using a MessageBox.
When you start anything from Task Scheduler, Windows blocks any "easy" ways to bring your windows or dialogs to top.
First way - use MB_SYSTEMMODAL (4096 value) flag. In my experience, it makes Msg dialog "Always on top".
win32ui.MessageBox("The website has changed.", "Website Change", MB_SYSTEMMODAL)
Second way - try to bring your console/window/dialog to the front with Following calls. Of course, if you use MessageBox you must do that (for your own created window) before calling MessageBox.
SetForegroundWindow(Wnd);
BringWindowToTop(Wnd);
SetForegroundWindow(Wnd);
As for flickering of the console window, you may try to start Python in a hidden state. For example, use ConEmu, ‘HidCon’ or cmdow. Refer to their parameters, something like:
ConEmu -basic -MinTSA -cmd C:\Python27\python.exe C:\pythonScript.py
or
CMDOW /RUN /MIN C:\Python27\python.exe C:\pythonScript.py
Avoiding the command window flash is done by naming the script with a pyw extension instead of simply py. You might also use pythonw.exe instead of python.exe, it really depends on your requirements.
See http://onlamp.com/pub/a/python/excerpts/chpt20/index.html?page=2
Use ctypes it displays an windows error message box very easy to use,
import ctypes
if condition:
ctypes.windll.user32.MessageBoxW(0, u"Error", u"Error", 0)
This works for me:
from ctypes import *
def MessageBox(title, text, style):
sty = int(style) + 4096
return windll.user32.MessageBoxW(0, text, title, sty) #MB_SYSTEMMODAL==4096
## Button Styles:
### 0:OK -- 1:OK|Cancel -- 2:Abort|Retry|Ignore -- 3:Yes|No|Cancel -- 4:Yes|No -- 5:Retry|No -- 6:Cancel|Try Again|Continue
## To also change icon, add these values to previous number
### 16 Stop-sign ### 32 Question-mark ### 48 Exclamation-point ### 64 Information-sign ('i' in a circle)
Usage:
MessageBox('Here is my Title', 'Message to be displayed', 64)
Making the message box system modal will cause it to pop up over every application, but none can be interacted with until it is dismissed. Consider either creating a custom dialog box window that you can bring to the front or using a notification bubble instead.
Windows tries to make it hard to pop a window over the active application. Users find it annoying, especially since the interrupting window generally steals keyboard focus.
The Windows way to give a notification like this is with a balloon in the notification area rather than a message box. Notification balloons don't steal focus and are (supposedly) less distracting.
I'm not sure if the python Windows UI library offers wrappers for notification balloons.
Very easy modal async message box with help of Python and MSG command (working on Win10):
# In CMD (you may use Username logged on target machine instead of * to send message to particular user):
msg /server:IP_or_ComputerName * /v /time:appearance_in_secs Message_up_to_255_chars
# I.e. send "Hello everybody!" to all users on 192.168.0.110 disappearing after 5 mins
msg /server:192.168.0.110 * /v /time:300 "Hello everybody!"
In Python I've been using subprocess to send CMD commands, allows me to read and process output, find error etc.
import subprocess as sp
name = 'Lucas'
message = f'Express yourself {name} in 255 characters ;)'
command = f'msg /server:192.168.0.110 * /v /time:360 "{message}"'
output = str(sp.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if 'returncode=0' in output:
pint('Message sent')
else:
print('Error occurred. Details:\n')
print(output[output.index('stdout=b'):])