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
Related
This question already has answers here:
Open document with default OS application in Python, both in Windows and Mac OS
(17 answers)
Closed 8 years ago.
I'm wondering how to open files in programs such as Notepad and Picture Viewer depending on the extension the file has. I'm using Python 3.3 on Windows.
I've done some research and people have mentioned a module named Image, but when I try and import this module I get an ImportError.
Here's what I have so far:
def openFile():
fileName = listbox_1.get(ACTIVE)
if fileName.endswith(".jpg"):
fileName.open()
I will also have HTML and JSON files that I will need to open in Notepad.
On Windows you could use os.startfile() to open a file using default application:
import os
os.startfile(filename)
There is no shutil.open() that would do it cross-platform. The close approximation is webbrowser.open():
import webbrowser
webbrowser.open(filename)
that might use automatically open command on OS X, os.startfile() on Windows, xdg-open or similar on Linux.
If you want to run a specific application then you could use subprocess module e.g., Popen() allows to start a program without waiting for it to complete:
import subprocess
p = subprocess.Popen(["notepad.exe", fileName])
# ... do other things while notepad is running
returncode = p.wait() # wait for notepad to exit
There are many ways to use the subprocess module to run programs e.g., subprocess.check_call(command) blocks until the command finishes and raises an exception if the command finishes with a nonzero exit code.
Use this to open any file with the default program:
import os
def openFile():
fileName = listbox_1.get(ACTIVE)
os.system("start " + fileName)
If you really want to use a certain program, such as notepad, you can do it like this:
import os
def openFile():
fileName = listbox_1.get(ACTIVE)
os.system("notepad.exe " + fileName)
Also if you need some if checks before opening the file, feel free to add them. This only shows you how to open the file.
Expanding on FatalError's suggestion with an example.
One additional benefit of using subprocessing rather than os.system is that it uses the same syntax cross-platform (os.system on Windows requires a "start" at the beginning, whereas OS X requires an "open". Not a huge deal, but one less thing to remember).
Opening a file with subprocess.call.
All you need to do to launch a program is call subprocess.call() and pass in a list of arguments where the first is the path to the program, and the rest are additional arguments that you want to supply to the program you're launching.
For instance, to launch Notepad.exe
import subprocess
path_to_notepad = 'C:\\Windows\\System32\\notepad.exe'
path_to_file = 'C:\\Users\\Desktop\\hello.txt'
subprocess.call([path_to_notepad, path_to_file])
Passing multiple arguments and paths is equally as simple. Just add additional items to the list.
Launching with multiple arguments
This, for example, launches a JAR file using a specific copy of the Java runtime environment.
import subprocess
import os
current_path = os.getcwd()
subprocess.call([current_path + '/contents/home/bin/java', # Param 1
'-jar', #Param2
current_path + '/Whoo.jar']) #param3
Argument 1 targets the program I want to launch. Argument2 supplies an argument to that program telling it that it's going to run a JAR, and finally Argument3 tells the target program where to find the file to open.
I have created shortcuts for executables and it works, but when I try to create one for a folder it does not work.
It does create a shortcut, it is just not the right 'Target Type'. Please take a look at the image below.
Instead of 'File', the target type should be 'File folder'. The problem is that when I open the shortcut it asks me which program do I want to open the File with and it does not open the folder.
The function I'm using to create the shortcuts is the following
from win32com.client import Dispatch
import winshell
import os
def create_shortcuts(self, tool_name, exe_path, startin, icon_path):
shell = Dispatch('WScript.Shell')
shortcut_file = os.path.join(winshell.desktop(), tool_name + '.lnk')
shortcut = shell.CreateShortCut(shortcut_file)
shortcut.Targetpath = exe_path
shortcut.WorkingDirectory = startin
shortcut.IconLocation = icon_path
shortcut.save()
I don't know if it's possible to set the 'Target Type'. I couldn't find a way to do it, but I do know there must be a way.
If you want to use .Net "clr" (especially if you already require it):
First run this... you will have to ship the output of this command with your application:
"c:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\TlbImp.exe" %SystemRoot%\system32\wshom.ocx /out:Interop.IWshRuntimeLibrary.dll
tlbimp.exe might even be in the path if you installed the Windows SDK in a fairly standard way. But if not, it's OK, you'll just ship the "assembly" (fancy word for interface-providing dll in .Net land) with your application.
Then this code will work in python:
import clr
sys.path.append(DIRECTORY_WHERE_YOU_PUT_THE_DLL)
clr.AddReference('Interop.IWshRuntimeLibrary')
import Interop.IWshRuntimeLibrary
sc = Interop.IWshRuntimeLibrary.WshShell().CreateShortcut("c:\\test\\sc.lnk")
isc = Interop.IWshRuntimeLibrary.IWshShortcut(sc)
isc.set_TargetPath("C:\\")
isc.Save()
.... the above code, with far too much modification and preamble, might even work with Mono.
For future reference: I observed the described behavior in python 3.9.6 when creating a shortcut to a non-existing directory, which was easily fixed by incorporating os.makedirs() into the method.
I've added a method parameter to the version I'm using, so it can handle shortcuts to files and directories:
def create_shortcuts(self, tool_name, exe_path, startin, icon_path, is_directory=False):
if is_directory:
os.makedirs(exe_path, exist_ok=True)
shell = Dispatch('WScript.Shell')
shortcut_file = os.path.join(winshell.desktop(), tool_name + '.lnk')
shortcut = shell.CreateShortCut(shortcut_file)
shortcut.Targetpath = exe_path
shortcut.WorkingDirectory = startin
shortcut.IconLocation = icon_path
shortcut.save()
Having code like:
from tkinter import filedialog
image_formats= [("JPEG", "*.jpg"), ("All files", "*.*")]
file=filedialog.askopenfilename(filetypes=image_formats)
I can open a file dialog box which leads me to a .jpg file.
On my Windows 7 development box this remembers over closing and opening the program the directory -- easy allowing selecting multiple files from the directory.
However, after distribution, using cx_Freeze and its bdist_msi option, the same program to a Windows 10 machine the directory is no longer remembered. How do I get the Windows 7 behaviour on the Windows 10 system? Preferably I do not perform this manually but rely on the underlying Windows mechanism.
PS Full bdist_msi distribution of the actual program is available at https://sites.google.com/site/klamerschutte/system/app/pages/admin/revisions?wuid=wuid:gx:403dfb9983518218
I know this is an old question but I happened to run into this issue recently. I tried this with the latest version of Python 3.7.
My way around this: Just don't add an initialdir argument. On Windows 10, it will start from the last used directory when the filedialog is called.
If I understand your question properly you want to know how to set the initial starting directory whenever the dialog is shown for selecting a file (of whatever types).
You may set this by the initialdir argument to askopenfilename which will take the path of the starting directory you want.
For example if I always wanted the dialog to open to the user's home folder then I could use os.path.expanduser('~') as the initialdir argument.
If you want it to remember the last directory used then get the parent directory of the file selected from the dialog using os.pardir and store it in a variable.
Try below code, it will remember the last directory used by the tool
filename = askopenfilename(parent=root,title='Choose template file', filetypes =[('Excel Files', '*.xlsx')])
Try this:
CORRECTION:
create a pages_to_connect_pages.py to allow variables go inside any class (from stackoverflow, I don´t remember the reference, sorry)
2.create a default directory in never_opened_directory.txt, to use first time
last_opened_directory.txt is created when you open a directory
use their references in pages_to_connect_pages.py
insert references in main app as bellow
[![create this file and all variables go to any class using Pages.something (took from stackoverflow, dont remember where from) ][1]][1]
[![create files to "remember" last directory opened after main app is closed][2]][2]
in main app:
my_app
from pages_to_connect_pages import Pages
# we see if the file last_opened_directory.txt is empty(never used)
filesize = os.path.getsize("last_opened_directory.txt")
# if Pages.last_opened_directory == '':
if filesize == 0:
Pages.never_opened_directory = open("never_opened_directory.txt", "r")
directory_to_open = Pages.never_opened_directory.read()
Pages.never_opened_directory.close()
# elif Pages.last_opened_directory != '':
elif filesize != 0:
Pages.last_opened_directory = open("last_opened_directory.txt", "r")
directory_to_open = Pages.last_opened_directory.read()
Pages.last_opened_directory.close()
else:
pass
reference:Python save variable even after closing the gui
If you want askopenfilename to remember last used directory, set initialdir to a nonexistent folder, something like initialdir = "/áéá".
This works for me on Windows 10.
Here's my code:
import easygui
f = easygui.fileopenbox()
print f
Seems simple, but when I run it, I can't select any of the files, see figure in link. Sorry if this is dumb, but I am at my wit's end!
http://imgur.com/c20TvQ5
EasyGui isn't supported anymore. On OS X I don't have this problem with fileopenbox (it looks like what happens with diropenbox actually.) I'd recommend you try something like wxPython. Here's how to get a file open box in that (from https://stackoverflow.com/a/9319832/866271)
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')
Tested on OS X with no problem. It's also cross-platform. If you're going to be doing GUI development, there's a lot of options to look at but wxPython is a good one because it uses the native widgets of whatever OS you're running. So everything looks pretty :)
For your case, you could instead call get_path('*.csv') if that's the type of file you're opening. Or just call get_path('*') to get all of them.
It seems that if I want to create a very basic Cocoa application with a dock icon and the like, I would have to use Xcode and the GUI builder (w/ PyObjC).
The application I am intending to write is largely concerned with algorithms and basic IO - and thus, not mostly related to Apple specific stuff.
Basically the application is supposed to run periodically (say, every 3 minutes) .. pull some information via AppleScript and write HTML files to a particular directory. I would like to add a Dock icon for this application .. mainly to showing the "status" of the process (for example, if there is an error .. the dock icon would have a red flag on it). Another advantage of the dock icon is that I can make it run on startup.
Additional bonus for defining the dock right-click menu in a simple way (eg: using Python lists of callables).
Can I achieve this without using Xcode or GUI builders but simply using Emacs and Python?
Install the latest py2app, then make a new directory -- cd to it -- in it make a HelloWorld.py file such as:
# generic Python imports
import datetime
import os
import sched
import sys
import tempfile
import threading
import time
# need PyObjC on sys.path...:
for d in sys.path:
if 'Extras' in d:
sys.path.append(d + '/PyObjC')
break
# objc-related imports
import objc
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
# all stuff related to the repeating-action
thesched = sched.scheduler(time.time, time.sleep)
def tick(n, writer):
writer(n)
thesched.enter(20.0, 10, tick, (n+1, writer))
fd, name = tempfile.mkstemp('.txt', 'hello', '/tmp');
print 'writing %r' % name
f = os.fdopen(fd, 'w')
f.write(datetime.datetime.now().isoformat())
f.write('\n')
f.close()
def schedule(writer):
pool = NSAutoreleasePool.alloc().init()
thesched.enter(0.0, 10, tick, (1, writer))
thesched.run()
# normally you'd want pool.drain() here, but since this function never
# ends until end of program (thesched.run never returns since each tick
# schedules a new one) that pool.drain would never execute here;-).
# objc-related stuff
class TheDelegate(NSObject):
statusbar = None
state = 'idle'
def applicationDidFinishLaunching_(self, notification):
statusbar = NSStatusBar.systemStatusBar()
self.statusitem = statusbar.statusItemWithLength_(
NSVariableStatusItemLength)
self.statusitem.setHighlightMode_(1)
self.statusitem.setToolTip_('Example')
self.statusitem.setTitle_('Example')
self.menu = NSMenu.alloc().init()
menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
'Quit', 'terminate:', '')
self.menu.addItem_(menuitem)
self.statusitem.setMenu_(self.menu)
def writer(self, s):
self.badge.setBadgeLabel_(str(s))
if __name__ == "__main__":
# prepare and set our delegate
app = NSApplication.sharedApplication()
delegate = TheDelegate.alloc().init()
app.setDelegate_(delegate)
delegate.badge = app.dockTile()
delegate.writer(0)
# on a separate thread, run the scheduler
t = threading.Thread(target=schedule, args=(delegate.writer,))
t.setDaemon(1)
t.start()
# let her rip!-)
AppHelper.runEventLoop()
Of course, in your real code, you'll be performing your own periodic actions every 3 minutes (rather than writing a temp file every 20 seconds as I'm doing here), doing your own status updates (rather than just showing a counter of the number of files written so far), etc, etc, but I hope this example shows you a viable starting point.
Then in Terminal.App cd to the directory containing this source file, py2applet --make-setup HelloWorld.py, python setup.py py2app -A -p PyObjC.
You now have in subdirectory dist a directory HelloWorld.app; open dist and drag the icon to the Dock, and you're all set (on your own machine -- distributing to other machines may not work due to the -A flag, but I had trouble building without it, probably due to mis-installed egg files laying around this machine;-). No doubt you'll want to customize your icon &c.
This doesn't do the "extra credit" thingy you asked for -- it's already a lot of code and I decided to stop here (the extra credit thingy may warrant a new question). Also, I'm not quite sure that all the incantations I'm performing here are actually necessary or useful; the docs are pretty latitant for making a pyobjc .app without Xcode, as you require, so I hacked this together from bits and pieces of example code found on the net plus a substantial amount of trial and error. Still, I hope it helps!-)
PyObjC, which is included with Mac OS X 10.5 and 10.6, is pretty close to what you're looking for.
Chuck is correct about PyObjC.
You should then read about this NSApplication method to change your icon.
-(void)setApplicationIconImage:(NSImage *)anImage;
For the dock menu, implement the following in an application delegate. You can build an NSMenu programmatically to avoid using InterfaceBuilder.
-(NSMenu *)applicationDockMenu:(NSApplication *)sender;