IronPython / PythonNET - Diálogos de Selección
FolderBrowserDialog, OpenFileDialog y SaveFileDialog
Introducción
Los diálogos de selección son formularios predefinidos para seleccionar un archivo o una carpeta, de manera que el path de este archivo o carpeta puede ser utilizado por el código de la aplicación.
Dentro de System.Windows.Forms
hay 3 tipos de diálogos predefinidos:
FolderBrowserDialog
- Seleccionar una carpeta cuyo path se almacena enFolderBrowserDialog.SelectedPath
.OpenFileDialog
- Seleccionar un archivo (con la intención de abrirlo) cuyo path se almacena enOpenFileDialog.FileName
.SaveFileDialog
- Seleccionar un archivo (con la intención de guardar contenido en él) cuyo path se almacena enSaveFileDialog.FileName
.
La única diferencia entre OpenFileDialog
y SaveFileDialog
es su título, ya que uno sugiere que va a abrirse y otro que va a guardarse un archivo. Sin embargo, lo que la aplicación haga con el path del archivo obtenido una vez se cierre el diálogo es cosa tuya, y es tu responsabilidad utilizar uno u otro cuando corresponda.
Estos diálogos son formualarios predefinidos con ciertas propiedades. Antes de empezar a utilizarlos observa el siguiente código:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import Form
form = Form()
result = form.ShowDialog()
print(repr(result)) # System.Windows.Forms.DialogResult.Cancel
Este script muestra un formulario vacío y cuando hacemos click en el botón X se cierra, momento en el que finaliza la ejecución de form.ShowDialog()
, que devuelve un resultado que en ironpython siempre es DialogResult.Cancel
, mientras que en pythonnet es 2
(ya que DialogResult.Cancel == 2
en pythonnet).
Los diálogos de selección siempre se usan como el formulario de este ejemplo: Tienen un método ShowDialog
que devuelve un resultado.
Para el formulario vacío del ejemplo anterior, el resultado siempre es DialogResult.Cancel
, pero para los diálogos de selección predefinidos el valor puede ser:
DialogResult.OK
- Ha habido selección (haciendo doble click en el archivo o carpeta, o pulsado el botón Aceptar).DialogResult.Cancel
- No ha habido selección (se ha hecho click en el Cancelar o en el botón X).
El método ShowDialog
puede invocarse tantas veces como se quiera, de modo que una instancia de diálogo puede utilizarse múltiples veces:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
form = Form()
label = Label()
label.TextAlign = ContentAlignment.MiddleCenter
label.Dock = DockStyle.Fill
label.Font = Font(FontFamily('Consolas'), 32.0, FontStyle.Bold, GraphicsUnit.Pixel)
form.Controls.Add(label)
for i in range(3):
form.Text = str(i+1)
label.Text = form.Text
form.ShowDialog()
FolderBrowserDialog
Un FolderBrowserDialog
tiene las siguientes propiedades:
.SelectedPath
- Es la carpeta seleccionada por defecto cuando se muestra el diálogo. SiShowDialog
devuelve el valorDialogResult.OK
se asigna la carpeta seleccionada a esta propiedad..ShowNewFolderButton
- Su valor por defecto esTrue
, y muestra un botón para crear una nueva carpeta.
Ejemplo de uso de FolderBrowserDialog
:
# -*- coding: utf-8 -*-
import threading, clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import *
def main():
dialog = FolderBrowserDialog()
dialog.SelectedPath = 'C:\\' # 'C:/' no funciona, hay que usar barras invertidas
dialog.Description = 'Haz el favor de seleccionar una carpeta...'
dialog.ShowNewFolderButton = True
r = dialog.ShowDialog()
if r == DialogResult.OK:
print('Has seleccionadao la carpeta {}'.format(dialog.SelectedPath))
elif r == DialogResult.Cancel:
print('No has seleccionado carpeta, última carpeta seleccionada es {}'.format(dialog.SelectedPath))
threading.Thread(target=main).start()
Observa que he definido una función main
que se ejecuta en un hilo separado: threading.Thread(target=main).start()
. Esto es necesario para pythonnet, pero no para ironpython.
También puedes utilizar los threads de .NET para conseguir el mismo resultado:
# -*- coding: utf-8 -*-
import clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Threading')
from System.Windows.Forms import *
from System.Threading import *
def main():
dialog = FolderBrowserDialog()
dialog.SelectedPath = 'C:\\' # 'C:/' no funciona, hay que usar barras invertidas
dialog.Description = 'Haz el favor de seleccionar una carpeta...'
dialog.ShowNewFolderButton = True
r = dialog.ShowDialog()
if r == DialogResult.OK:
print('Has seleccionadao la carpeta {}'.format(dialog.SelectedPath))
elif r == DialogResult.Cancel:
print('No has seleccionado carpeta, última carpeta seleccionada es {}'.format(dialog.SelectedPath))
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
OpenFileDialog y SaveFileDialog
Tanto OpenFileDialog
como SaveFileDialog
tienen las siguientes propiedades:
.InitialDirectory
- Directorio en el que se inicia el diálogo..FileName
- Es el archivo seleccionado por defecto cuando se muestra el diálogo. SiShowDialog
devuelve el valorDialogResult.OK
se asigna el archivo seleccionado a esta propiedad..DefaultExt
- Extensión por defecto del archivo. Si el usuario escribe un nombre de archivo y omite la extensión, esta se agrega automáticamente..Filter
- Filtro de archivos. Ejemplos:'txt|*.txt'
- Sólo se permiten archivos con la extensión txt.'all|*.*'
- Se permiten archivos con cualquier extensión.'txt|*.txt|all|*.*'
- Por defecto se buscan archivos txt, pero el usuario puede indicar explícitamente que quiere buscar cualquier tipo de archivo.'bmp|*.bmp|jpg|*.jpg|png|*.png|gif|*.gif'
- Por defecto se buscan archivos bmp, pero pueden seleccionarse otros formatos de imagen.'scripts python|*.py;*.pyw'
- Solamente se permiten archivos .py o .pyw.
SaveFileDialog
además tiene la propiedad .OverwritePrompt
: Su valor por defecto es True
, de modo que si se selecciona un archivo que ya existe se muestra un mensaje de confirmación para que el usuario indique si quiere sobreescribir el archivo. Si su valor es False
no se muestra ningún mensaje.
Ejemplo de OpenFileDialog
# -*- coding: utf-8 -*-
import threading, clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import OpenFileDialog, DialogResult
def main():
dialog = OpenFileDialog()
dialog.InitialDirectory = 'C:\\'
dialog.DefaultExt = 'txt'
dialog.Filter = 'txt|*.txt|all|*.*'
dialog.FileName = 'nombre_archivo.txt'
if dialog.ShowDialog() == DialogResult.OK:
with open(dialog.FileName, 'r') as f:
print(f.read())
else:
print('No has seleccionado ningún archivo.')
threading.Thread(target=main).start()
Puesto que hemos utilizado un OpenFileDialog
, al obtener su FileName
el lógico usarlo para leer el contenido del archivo:
with open(dialog.FileName, 'r') as f:
print(f.read())
Hemos hecho una lectura en modo texto, pero podrías utilizar open(dialog.FileName, 'rb')
para hacer una lectura en modo binario.
Ejemplo de SaveFileDialog
# -*- coding: utf-8 -*-
import sys, clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Threading')
from System.Windows.Forms import SaveFileDialog, DialogResult
from System.Threading import *
def main():
user_input = raw_input if int(sys.version[0]) < 3 else input
texto = user_input('Escribe algo: ')
dialog = SaveFileDialog()
dialog.InitialDirectory = 'C:\\'
dialog.DefaultExt = 'txt'
dialog.Filter = 'txt|*.txt|all|*.*'
dialog.FileName = 'nombre_archivo.txt'
if dialog.ShowDialog() == DialogResult.OK:
with open(dialog.FileName, 'w') as f:
f.write(texto)
else:
print('El texto no ha sido guardado.')
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
Puesto que hemos utilizado un SaveFileDialog
, al obtener su FileName
el lógico usarlo para escribir en el archivo:
with open(dialog.FileName, 'w') as f:
f.write(texto)
También podrías hacer una escritura binaria utilizando open(dialog.FileName, 'wb')
.
Una vez seleccionado el nombre de archivo no hemos comprobado si este existe o no. No es necesario hacer esta comprobación, ya que SaveFileDialog
muestra un MessageBox
de confirmación en caso de que el archivo ya exista, para que el usuario decida si quiere sobreescribir el archivo o no.
Si no quieres que se muestre este mensaje de confirmación puedes asignar el valor False
a la propiedad SaveFileDialog.OverwritePrompt
. En este caso sería lógico utilizar open(dialog.FileName, 'a')
:
dialog = SaveFileDialog()
dialog.OverwritePrompt = False
if dialog.ShowDialog() == DialogResult.OK:
with open(dialog.FileName, 'a') as f:
f.write('texto añadido al archivo')
Ejemplo de Aplicación completa
El siguiente script es una aplicación que tiene un menú para abrir, guardar y cerrar archivos con la extensión .py. La aplicación permite editar y ejecutar scripts ironpython:
# -*- coding: utf-8 -*-
import sys, os, clr
for reference in 'System', 'System.Windows.Forms', 'System.Drawing', 'System.Threading':
clr.AddReference(reference)
import System
from System.Windows.Forms import *
from System.Drawing import *
from System.Threading import *
class MainForm(Form):
def __init__(self):
self.Text = 'IronPython Editor'
self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
self.ClientSize = Size(640, 480)
self.Load += lambda *args: self.__setattr__('MinimumSize', self.Size) # self.MinimumSize = self.Size (una vez se ha cargado el formulario)
# CREAR MENÚ
self.Menu = MainMenu()
self.open_item, self.save_item, self.close_item = [i() for i in [MenuItem] * 3]
self.open_item.Text, self.save_item.Text, self.close_item.Text = 'Abrir', 'Guardar', 'Cerrar'
self.open_item.Click += self.show_open_file_dialog
self.save_item.Click += self.show_save_file_dialog
self.close_item.Click += self.close_file
self.save_item.Enabled = self.close_item.Enabled = False
for i in self.open_item, self.save_item, self.close_item:
self.Menu.MenuItems.Add(i)
# CREAR CONTROLES
self.rchCode = RichTextBox()
self.rchCode.Dock = DockStyle.Fill
self.rchCode.AcceptsTab = True
self.rchCode.Font = Font(FontFamily('Consolas'), 14.0, FontStyle.Regular, GraphicsUnit.Pixel)
self.rchCode.ForeColor = Color.Blue
self.rchCode.KeyUp += self.enable_or_disable_save_item
self.btnExec = Button()
self.btnExec.Text = 'RUN'
self.btnExec.Height = 50
self.btnExec.Dock = DockStyle.Bottom
self.btnExec.Click += self.execute_code
self.Controls.Add(self.rchCode)
self.Controls.Add(self.btnExec)
# CREAR DIÁLOGOS
self.open_file_dialog = OpenFileDialog()
self.open_file_dialog.DefaultExt = 'py'
self.open_file_dialog.Filter = 'scripts python|*.py;*.pyw'
self.save_file_dialog = SaveFileDialog()
self.save_file_dialog.DefaultExt = 'py'
self.save_file_dialog.Filter = 'scripts python|*.py;*.pyw'
def show_open_file_dialog(self, *args):
if self.open_file_dialog.FileName:
self.open_file_dialog.InitialDirectory, self.open_file_dialog.FileName = os.path.split(self.open_file_dialog.FileName)
if self.open_file_dialog.ShowDialog() == DialogResult.OK:
with open(self.open_file_dialog.FileName, 'rb') as f:
self.rchCode.Text = f.read().decode('latin-1') # self.Invoke(System.Action(lambda:setattr(self.rchCode, 'Text', f.read().decode('latin-1'))))
self.save_item.Enabled = self.close_item.Enabled = True
def show_save_file_dialog(self, *args):
if self.open_file_dialog.FileName:
self.save_file_dialog.InitialDirectory, self.save_file_dialog.FileName = os.path.split(self.open_file_dialog.FileName)
if self.save_file_dialog.ShowDialog() == DialogResult.OK:
with open(self.save_file_dialog.FileName, 'w') as f:
f.write(self.rchCode.Text)
self.save_item.Enabled = self.close_item.Enabled = True
def close_file(self, *args):
self.rchCode.Text = ''
self.save_item.Enabled = self.close_item.Enabled = False
self.open_file_dialog.InitialDirectory = self.save_file_dialog.InitialDirectory = None
self.open_file_dialog.FileName = self.save_file_dialog.FileName = None
def enable_or_disable_save_item(self, *args):
self.save_item.Enabled = bool(self.rchCode.Text)
def execute_code(self, *args):
if self.rchCode.Text:
try:
namespace = {} # Evitar acceso a variables de la aplicación
exec(self.rchCode.Text, namespace)
except Exception as e:
MessageBox.Show(str(e), 'ERROR', MessageBoxButtons.OK, MessageBoxIcon.Error)
def main():
Application.EnableVisualStyles()
Application.Run(MainForm())
if sys.implementation.name == 'cpython':
thread = Thread(ThreadStart(main))
thread.SetApartmentState(ApartmentState.STA)
thread.Start()
thread.Join()
elif sys.implementation.name == 'ironpython':
main()