Está en la página 1de 74

Poo Y TKINTER -1-

miércoles, 3 de junio de 2015


Programación Orientada a Objetos (I)

Introducción

Un Programa Orientado a Objetos (POO) se basa en una agrupación de objetos de


distintas clases que interactúan entre sí y que, en conjunto, consiguen que un programa
cumpla su propósito.
En este paradigma de programación se intenta emular el funcionamiento de los objetos
que nos rodean en la vida real.En Python cualquier elemento del lenguaje pertenece a
una clase y todas las clases tienen el mismo rango y se utilizan del mismo modo.A
continuación, se declaran varios objetos Python y con la función type() se muestra a qué
clase pertenecen cada uno:

lenguaje = "Python"
print(type(lenguaje))  # class str=""
def mitad(x):
    return x/2

print(type(mitad))  # class function=""


valor = mitad(25)
print(type(valor))  # class float=""
import os
print(type(os))  # class module=""
lista = [1, 2, 3, 4, 5]
print(type(lista))  # class list=""

<class 'str'>
<class 'function'>
<class 'float'>
<class 'module'>
<class 'list'>
Poo Y TKINTER -2-

Clases, atríbutos y métodos

Las clases en este contexto permiten definir los atributos y el comportamiento,


mediante métodos, de los objetos de un programa. Una clase es una especie de plantilla
o prototipo que se utiliza para crear instancias individuales del mismo tipo de objeto.
Los atributos definen las características propias del objeto y modifican su estado. Son
datos asociados a las clases y a los objetos creados a partir de ellas.De ello, se deducen
los dos tipos de atributos o de variables existentes: variables de clase y variables de
instancia (objetos).
Los métodos son bloques de código (o funciones) de una clase que se utilizan para
definir el comportamiento de los objetos Tanto para acceder a los atributos como para
llamar a los métodos se utiliza el método denominado de notación de punto que se basa
en escribir el nombre del objeto o de la clase seguido de un punto y el nombre del atributo
o del método con los argumentos que procedan :
clase.atributo, objeto.atributo, objeto.método([argumentos]).

Un ejemplo característico de objeto Python donde se identifican fácilmente los métodos


son las listas. Una lista es un objeto que permite contener una colección o secuencia de
datos. Los datos de una lista deben ir separados por comas (,) y todo el conjunto entre
corchetes. Una lista es una estructura mutable porque no sólo se puede acceder a los
datos, además, es posible agregar nuevos elementos o suprimir aquellos que no sean
necesarios. La clase lista (List) incorpora varios métodos para facilitar este trabajo:

lista = ['c', 'a', 'b']  # Declara lista con tres elementos


lista.append('d')  # Agrega elemento al final de lista con append()
lista.pop()  # Borra último elemento de lista con método pop()
lista.sort()  # Ordena la lista con el método sort()
print(lista)  # ['a', 'b', 'c']

'a', 'b', 'c']

Características de la Programación Orientada a Objeto

Características que definen a este modelo de programación


Abstracción

Se refiere a que un elemento pueda aislarse del resto de elementos y de su contexto para
centrar el interés en lo qué hace y no en cómo lo hace (caja negra). 
Modularidad
Es la capacidad de dividir una aplicación en partes más pequeñas independientes y
reutilizables llamadas módulos.
Poo Y TKINTER -3-
Encapsulación
Consiste en reunir todos los elementos posibles de una entidad al mismo nivel de
abstracción para aumentar la cohesión, contando con la posibilidad de ocultar los
atributos de un objeto (en Python, sólo se ocultan en apariencia).
Herencia
se refiere a que una clase pueda heredar las características de una clase superior para
obtener objetos similares. Se heredan tanto los atributos como los métodos. Éstos últimos
pueden sobrescribirse para adaptarlos a las necesidades de la nueva clase. A la
posibilidad de heredar atributos y métodos de varias clases se denomina Herencia
Múltiple
Polimorfismo Alude a la posibilidad de identificar de la misma forma comportamientos
similares asociados a objetos distintos. La idea es que se sigan siempre las mismas
pautas aunque los objetos y los resultados sean otros.

Variables de Clases y Variables de Instancias

En lenguajes que crean objetos a partir de clases, un objeto es una instancia de una
clase. Y de una misma clase se pueden mantener activas en un programa más de una
instancia al mismo tiempo.
Una variable de clase es compartida por todas las instancias de una clase. Se definen
dentro de la clase (después del encabezado de la clase) pero nunca dentro de un método.
Este tipo de variables no se utilizan con tanta frecuencia como las variables de instancia.
Una variable de instancia se define dentro de un método y pertenece a un objeto
determinado de la clase instanciada.

Crear clases

Una clase consta de dos partes: un encabezado que comienza con el


término class seguido del nombre de la clase (en singular) y dos puntos (:) y
un cuerpo donde se declaran los atributos y los métodos:
class NombreClase:
    'Texto para documentar la clase (opcional)'
    varclase1 = "variable de clase1"

    def nombremetodo1(self, var1):


        self.var1 = var1

    def nombremetodo2(self):
        self.var1
Poo Y TKINTER -4-
La documentación de una clase debe situarse después del encabezado y justo antes
del lugar donde se declaren las variables y los métodos de la clase.
Desde cualquier lugar de un programa se puede acceder a la cadena de documentación
de una clase accediendo al atributo especial: NombreClase.__doc__ Todo lo que se
incluye en una clase es opcional. De hecho, la clase más elemental aunque no tenga
mucha utilidad puede estar vacía:

class Clase:
pass

La declaración pass indica que no se ejecutará ningún código. Sin embargo, esta clase


una vez definida permite que se instancien objetos de ella e incluso es posible realizar
algunas operaciones elementales.

objeto1 = Clase() # Crea objeto1 de clase Clase


objeto2 = Clase() # Crea objeto2 de clase Clase
print(objeto1 == objeto2) # Retorna False...
# Los objetos aunque sean de la misma clase son diferentes.

Explicación de direcciones y valores de clase y objeto


class Clase:
    """
    este es unejemplo de documentacion
    de clase
    """
print(Clase.__doc__)
objeto1 = Clase()  # Crea objeto1 de clase Clase
objeto2 = Clase()  # Crea objeto2 de clase Clase
print(objeto1 == objeto2)  # Retorna False...
print("objeto1",objeto1)
print("objeto2",objeto2)
print("id objeto1",id(objeto1))
print("id objeto2",id(objeto2))
print("clase",Clase)
print("id(clase)",id(Clase))

En el siguiente ejemplo se define una clase mucho más completa:

class Alumno:
'Clase para alumnos'
numalumnos = 0
sumanotas = 0
Poo Y TKINTER -5-

def __init__(self, nombre, nota):


self.nombre = nombre
self.nota = nota
Alumno.numalumnos += 1
Alumno.sumanotas += nota

def mostrarNombreNota(self):
return(self.nombre, self.nota)

def mostrarNumAlumnos(self):
return(Alumno.numalumnos)

def mostrarSumaNotas(self):
return(Alumno.sumanotas)

def mostrarNotaMedia(self):
if Alumno.numalumnos > 0:
return(Alumno.sumanotas/Alumno.numalumnos)
else:
return("Sin alumnos")

La clase Alumno consta de dos variables de clase


(Alumno.numalumnos y Alumno.sumanotas) que son accesibles desde los métodos de
la clase. Además, sus valores son compartidos por todas las instancias que existan de
esta clase.
A continuación, se declaran varios métodos (funciones) que incluyen como primer
argumento a self que contiene la referencia del objeto especifico que llama al método en
un momento dado. Como su valor es implícito cuando se llama a un método no es
necesario pasar este argumento.

El método __init__() es especial porque se ejecuta automáticamente cada vez que se


crea una nuevo objeto. Este método, que es opcional, se llama constructor y se suele
utilizar para inicializar las variables de las instancias (en este caso para inicializar las
variables self.nombre y self.nota).

El resto de métodos se utilizan para acceder y mostrar el valor de las variables de clase y
de instancia. Por último, el método mostrarNotaMedia() realiza un cálculo y después
muestra su resultado.

Crear objetos (instancias) de una clase

Para crear instancias de una clase se llama a la clase por su propio nombre pasando los
argumentos que requiera el método constructor __init__ si existe.
Poo Y TKINTER -6-

alumno1 = Alumno("Maria", 8)
alumno2 = Alumno("Carlos", 6)

Todos los argumentos se pasan escribiéndolos entre paréntesis y separados por comas
(","). El primer argumento self se omite porque su valor es una referencia al objeto y es
implícito para todos los métodos.

Accediendo a los atributos y llamando a los métodos

Para acceder a la variable de un objeto se indica el nombre del objeto, seguido de un


punto y el nombre de la variable:

print(alumno1.nombre) # María
print(alumno1.nota) # 8

Para modificar la variable de un objeto se utiliza la misma notación para referirse al


atributo y después del signo igual (=) se indica la nueva asignación:

alumno1.nombre = "Carmela"

Para acceder a las variables de la clase se sigue la misma notación pero en vez de indicar
el nombre del objeto se indica el nombre de la clase instanciada.

print(Alumno.numalumnos) # 2
print(Alumno.sumanotas) # 14

Para llamar a un método se indica el nombre de objeto, seguido de un punto y el nombre


del método. Si se requieren varios argumentos se pasan escribiéndolos entre paréntesis,
separados por comas (","). Si no es necesario pasar argumentos se añaden los paréntesis
vacíos "()" al nombre del método.

print(alumno1.mostrarNombreNota()) # ('Carmen', 8)
print(alumno2.mostrarNombreNota()) # ('Carlos', 6)

Para suprimir un atributo:

del alumno1.nombre
Poo Y TKINTER -7-

Si a continuación, se intenta acceder al valor del atributo borrado o se llama a algún


método que lo utilice, se producirá la siguiente excepción:

print(alumno1.mostrarNombreNota())

AttributeError: 'Alumno' object has no attribute 'nombre'

Pare crear nuevamente el atributo realizar una nueva asignación:

alumno1.nombre = "Carmen"

programa completo
class Alumno:
    'Clase para alumnos'
    numalumnos = 0
    sumanotas = 0

    def __init__(self, nombre, nota):


        self.nombre = nombre
        self.nota = nota
        Alumno.numalumnos += 1
        Alumno.sumanotas += nota

    def mostrarNombreNota(self):
        return(self.nombre, self.nota)

    def mostrarNumAlumnos(self):
        return(Alumno.numalumnos)

    def mostrarSumaNotas(self):
        return(Alumno.sumanotas)

    def mostrarNotaMedia(self):
        if Alumno.numalumnos > 0:
            return(Alumno.sumanotas/Alumno.numalumnos)
        else:
            return("Sin alumnos")
alumno1 = Alumno("Maria", 8)
alumno2 = Alumno("Carlos", 6)
print(alumno1.nombre)  # María
print(alumno1.nota)  # 8
alumno1.nombre = "Carmela"
print(Alumno.numalumnos)  # 2
print(Alumno.sumanotas)  #
print(alumno1.mostrarNombreNota())  # ('Carmen', 8)
Poo Y TKINTER -8-
print(alumno2.mostrarNombreNota())  # ('Carlos', 6)

del alumno1.nombre
# print(alumno1.mostrarNombreNota())  erroja error
alumno1.nombre = "Carmen"

Maria
8
2
14
('Carmela', 8)
('Carlos', 6)
PS E:\impacientes>

Programación Orientada a Objetos (II)

Funciones para atributos: getattr(), hasattr(), setattr() y delattr()

getattr()

La función getattr() se utiliza para acceder al valor del atributo de un objeto Si un atributo


no existe retorna el valor del tercer argumento (es opcional).

nota_alumno2 = getattr(alumno2, 'nota', 0)


edad_alumno2 = getattr(alumno2, 'edad', 0)
suma_notas = getattr(Alumno, 'sumanotas')
print(nota_alumno2) # 6
print(edad_alumno2) # 0
print(suma_notas) # 15

hasattr()

La función hasattr() devuelve True o False dependiendo si existe o no el atributo


indicado.
Poo Y TKINTER -9-

if not hasattr(alumno2, 'edad'):


print("El atributo 'edad' no existe")

setattr()

Se utiliza para asignar un valor a un atributo. Si el atributo no existe entonces será creado.

setattr(alumno2, 'edad', 18)


print(alumno2) # 18

delattr()

La función delattr() es para borrar el atributo de un objeto. Si el atributo no existe se


producirá una excepción del tipo AttributeError.

delattr(alumno2, 'edad')
delattr(alumno2, 'curso')

Atributos de clase (Built-In)

Todas las clases Python incorporan los siguientes atributos especiales:

__dict__

Devuelve un diccionario que contiene el espacio de nombres de la clase (o de un objeto


instanciado).

print(Alumno.__dict__)

{'mostrarSumaNotas': function 0xb7071854="" alumno.mostrarsumanotas="" at="",


'mostrarNotaMedia': function 0xb707189c="" alumno.mostrarnotamedia="" at="",
'__doc__': 'Clase para alumnos', '__init__': function 0xb70717c4="" alumno.__init__=""
at="", '__weakref__': attribute lumno="" objects="" of="" weakref__="", '__dict__': attribute
dict__="" lumno="" objects="" of="", 'mostrarNumAlumnos': function 0xb7071734=""
alumno.mostrarnumalumnos="" at="", 'sumanotas': 14, '__module__': '__main__',
'numalumnos': 2, 'mostrarNombreNota': function 0xb707177c=""
alumno.mostrarnombrenota="" at=""}
Poo Y TKINTER -10-

print(alumno1.__dict__)

# {'nombre': 'Carmen', 'nota': 8}

__name__

Devuelve el nombre de la clase.

print(Alumno.__name__)

# Alumno

__doc__

Devuelve la cadena de documentación de la clase o None si no se ha definido.

print(Alumno.__doc__)

# Clase para alumnos

__module__

Devuelve el nombre del módulo donde se encuentra la clase definida.

print(Alumno.__module__) # __main__

from datetime import date


print(date.__module__)

# datetime

__bases__

Devuelve una tupla (posiblemente vacía) que contiene las clases base, en orden de
aparición.

print(Alumno.__bases__)

# (class object="",)
Poo Y TKINTER -11-
A continuación, se define la clase Becario a partir de la clase Alumno. La
clase Becario hereda los atributos y métodos de la clase Alumno. Al acceder al
atributo __bases__ de la nueva clase retorna la referencia a la clase padre Alumno.

from p5 import *
class Becario(Alumno):
    pass

print(Becario.__bases__)  

<class 'p5.Alumno'>,)

class Becario(Alumno):
pass

print(Becario.__bases__)

# (class main__.alumno="",)

Destrucción de objetos (Recolección de basura)

Python elimina objetos que no son necesarios (del tipo Built-In o instancias de clase)
automáticamente para liberar espacio de memoria. El proceso por el cual Python reclama
periódicamente bloques de memoria que ya no están en uso se denomina Recolección
de Basura.

La Recolección de Basura de Python se ejecuta durante la ejecución de un programa y


se activa cuando el contador de referencia de un objeto llega al valor cero. Este contador
va cambiando su valor en función del uso que se haga del objeto. Si se asigna un nuevo
nombre o se utiliza en una lista, tupla o diccionario el contador irá en aumento y si se
borra el objeto con del, su referencia es reasignada o bien se queda fuera de ámbito, va
disminuyendo. En el momento que se alcanza el valor cero el recolector comienza con la
tarea de "rescate".

Ejemplo:

pizza1 = "Margarita" # Crear objeto margarita


pizza2 = pizza1 # Incrementa el contador de referencia de margarita

cena['lunes'] = pizza2 # Incrementa contador de referencia margarita

del pizza1 # Decrementa el contador de referencia de margarita


pizza2 = "Cuatro Estaciones" # Decrementa contador ref. margarita
cena['lunes'] = pizza2 # Decrementa contador referencia margarita
Poo Y TKINTER -12-

El trabajo del recolector pasa totalmente desapercibido para un usuario. Actuará


automáticamente cuando detecte instancias huérfanas, recuperando su espacio de
memoria.

No obstante, una clase puede incluir el método __del__(), llamado destructor, que se


invoca cuando una instancia está a punto de ser destruida con del, o si no ha sido
destruida con esta sentencia, cuando el programa finalice su ejecución.

Este método especial puede ser utilizado para limpiar aquellos recursos de memoria no
usados por una instancia.
Ejemplo:

class Robot:
'Clase Robot'
def __init__(self, nombre):
self.nombre = nombre

def saludo(self):
print("Hola, mi nombre es", self.nombre)

def __del__(self):
print("Se han terminado mis baterias. Me voy a dormir.")

robot1 = Robot("C7PQ")
robot1.saludo() # Hola, mi nombre es C7PQ
print(id(robot1)) # Muestra el ID del objeto, Ejem.: 3070861004
del robot1 # Se han terminado mis baterias. Me voy a dormir.

Si intentamos mostrar de nuevo el ID del objeto, llamar al método saludo() o acceder al


atributo nombre se producirá el siguiente error:

print(id(robot1))
robot1.saludo()
print(robot1.nombre)

NameError: name 'robot1' is not defined

Es recomendable definir las clases en un archivo separado. Más adelante, cuando se


deseen utilizar las clases en un programa se importará el archivo con la sentencia import.
Poo Y TKINTER -13-
Herencia

La herencia es una de las características más importantes de la Programación Orientada


a Objetos. Consiste en la posibilidad de crear una nueva clase a partir de una o más
clases existentes, heredando de ellas sus atributos y métodos que podrán utilizarse como
si estuvieran definidos en la clase hija.
Las clases derivadas se declaran como cualquier clase con la diferencia de incluir
después de su nombre el nombre de la clase superior (entre paréntesis) de la que
heredará sus características:

class NombreSubClase (NombreClaseSuperior):


'Cadena de documentación opcional'
Declaración de atributos y métodos...

Ejemplo:

class volumen:
'Clase para controlar volumen de un reproductor multimedia'
def __init__(self): # método constructor de objeto. Activa volumen
self.nivel = 3 # sitúa el nivel de volumen en 3
print('nivel', self.__class__.__name__, self.nivel)

def subir(self): # método para subir el nivel de volumen de 1 en 1


self.nivel += 1
if self.nivel > 10: # al intentar sobrepasar el nivel 10
self.nivel = 10 # el nivel permanece en 10

print('nivel', self.__class__.__name__, self.nivel)

def bajar(self): # método para bajar el nivel de 1 en 1


self.nivel -= 1
if self.nivel < 0: # al intentar bajar por debajo del nivel 0
self.nivel = 0 # el nivel permanece en 0

print('nivel', self.__class__.__name__, self.nivel)

def silenciar(self): # método para silenciar


self.nivel = 0 # el nivel se sitúa en el 0
print('nivel', self.__class__.__name__, self.nivel)

class graves(volumen): # crea clase graves a partir de clase volumen


pass

ControlVolumen = volumen() # crea objeto y activa el volumen en 3


ControlVolumen.subir() # sube el volumen del nivel 3 al 4
ControlVolumen.bajar() # baja el volumen del nivel 4 al 3
ControlVolumen.silenciar() # silencia volumen bajando del 3 al 0
Poo Y TKINTER -14-

ControlGraves = graves() # crea objeto control graves, activa nivel 3


ControlGraves.subir() # sube el nivel de graves del 3 nivel al 4
del ControlVolumen # borra el objeto
del ControlGraves # borra el objeto

Herencia múltiple

La herencia múltiple se refiere a la posibilidad de crear una clase a partir de múltiples


clases superiores. Es importante nombrar adecuadamente los atributos y los métodos en
cada clase para no crear conflictos:

Ejemplo:

class Telefono:
"Clase teléfono"
def __init__(self):
pass
def telefonear(self):
print('llamando')
def colgar(self):
print('colgando')

class Camara:
"Clase camara fotográfica"
def __init__(self):
pass
def fotografiar(self):
print('fotografiando')

class Reproductor:
"Clase Reproductor Mp3"
def __init__(self):
pass
def reproducirmp3(self):
print('reproduciendo mp3')
def reproducirvideo(self):
print('reproduciendo video')

class Movil(Telefono, Camara, Reproductor):


def __del__(self):
print('Móvil apagado')

movil1 = Movil()
print(dir(movil1))
movil1.reproducirmp3()
movil1.telefonear()
movil1.fotografiar()
del movil1
Poo Y TKINTER -15-

Salida:

['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',


'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'colgar', 'fotografiar', 'reproducirmp3',
'reproducirvideo', 'telefonear']

reproduciendo mp3
llamando
fotografiando
Móvil apagado

Funciones issubclass() y isinstance()

La función issubclass(SubClase, ClaseSup) se utiliza para comprobar si una clase


(SubClase) es hija de otra superior (ClaseSup), devolviendo True o False según sea el
caso.

print(issubclass(Movil, Telefono)) # True


print(issubclass(Movil, Reproductor)) # True

La función booleana isinstance(Objeto, Clase) se utiliza para comprobar si un objeto


pertenece a una clase o clase superior.

movil2 = Movil()
print(isinstance(movil2, Movil)) # True
print(isinstance(movil2, Camara)) # True

Continúa en: Programación Orientada a Objetos (y III)


Poo Y TKINTER -16-
miércoles, 3 de junio de 2015
Programación Orientada a Objetos (y III)

Polimorfismo: Sobrecarga de métodos (Overriding Methods)

La sobrecarga de métodos se refiere a la posibilidad de que una subclase cuente con


métodos con el mismo nombre que los de una clase superior pero que definan
comportamientos diferentes.
En el siguiente ejemplo se redefinen dos métodos que son heredados de clases
superiores: __init__ y reproducirmp3()

class Movil(Telefono, Camara, Reproductor):


def __init__(self):
print('Móvil encendido')
def reproducirmp3(self):
print('Reproduciendo lista mp3')
def __del__(self):
print('Móvil apagado')

movil3 = Movil() # Móvil encendido


movil3.reproducirmp3() # Reproduciendo lista mp3
del movil3 # Móvil apagado

La siguiente lista enumera algunos métodos especiales que se pueden sobreescribir para
que tengan comportamientos diferentes:

__init__ (self [,args...]): Método constructor que se ejecuta al crear un objeto. Sus
argumentos son opcionales: objeto = NombreClase(argumentos)

__del__(self): Método destructor que se ejecuta al suprimir un objeto: del objeto

__repr__(self): Método que se ejecuta cuando se utiliza la función repr(objeto) para


Poo Y TKINTER -17-
convertir datos del objeto a cadenas expresadas como representaciones legibles por el
intérprete Python: repr(objeto)
__str__(self): Método que se ejecuta cuando imprimimos una instancia del objeto
con print(objeto) o llamamos a la función str(objeto) para convertir datos a cadenas
imprimibles. A diferencia de __repr__ la cadena resultante no necesita ser una expresión
Python válida: str(objeto)

Polimorfismo: Sobrecarga de Operadores (Overloading Operators)

La sobrecarga de operadores trata básicamente de lo mismo que la sobrecarga de


métodos pero pertenece en esencia al ámbito de los operadores aritméticos, binarios, de
comparación y lógicos.

class Punto:
def __init__(self,x = 0,y = 0):
self.x = x
self.y = y

def __add__(self,other):
x = self.x + other.x
y = self.y + other.y
return x, y

punto1 = Punto(4,6)
punto2 = Punto(1,-2)
print(punto1 + punto2) # (5, 4)

Ocultación de datos (Encapsulación)

Los atributos de un objeto pueden ocultarse (superficialmente) para que no sean


accedidos desde fuera de la definición de una clase. Para ello, es necesario nombrar los
atributos con un prefijo de doble subrayado: __atributo

class Factura:
__tasa = 19

def __init__(self, unidad, precio):


self.unidad = unidad
self.precio = precio

def a_pagar(self):
total = self.unidad * self.precio
impuesto = total * Factura.__tasa / 100
return(total + impuesto)
Poo Y TKINTER -18-

compra1 = Factura(12, 110)


print(compra1.unidad)
print(compra1.precio)
print(compra1.a_pagar(), "euros")
print(Factura.__tasa) # Error:

# AttributeError: type object 'Factura' has no attribute '__tasa'

Python protege estos atributos cambiando su nombre internamente. A sus nombres


agrega el nombre de la clase:
objeto._NombreClase__NombreAtributo.

print(compra1._Factura__tasa)

# 19

Propiedades (properties)

Cuando se trabajan con clases es recomendable crear atributos ocultos y utilizar métodos
específicos para acceder a los mismos para establecer, obtener o borrar la información:

class Empleado:
def __init__(self, nombre, salario):
self.__nombre = nombre
self.__salario = salario

def getnombre(self):
return self.__nombre

def getsalario(self):
return self.__salario

def setnombre(self, nombre):


self.__nombre = nombre

def setsalario(self, salario):


self.__salario = salario

def delnombre(self):
del self.__nombre

def delsalario(self):
del self.__salario

empleado1 = Empleado("Francisco", 30000)


Poo Y TKINTER -19-

print(empleado1.getnombre())
empleado1.setnombre("Francisco José")
print(empleado1.getnombre(), ",", empleado1.getsalario())

Estos métodos son útiles principalmente para los atributos más importantes de un objeto,
generalmente aquellos que necesitan ser accedidos desde otros objetos
Pero hay otra alternativa al uso de estos métodos basada en las propiedades Python que
simplifica la tarea. Las propiedades en Python son un tipo especial de atributo a los que
se accede a través de llamadas a métodos. Con ello, es posible ocultar los métodos "get",
"set" y "del" de manera que sólo es posible acceder mediante estas propiedades por ser
públicas.

No es obligatorio definir métodos "get", "set" y "del" para todas las propiedades. Es
recomendable sólo para aquellos atributos en los que sea necesario algún tipo de
validación anterior a establecer, obtener o borrar un valor. Para que una propiedad sea
sólo de lectura hay que omitir las llamadas a los métodos "set" y "del".

class Empleado():
def __init__(self, nombre, salario):
self.__nombre = nombre
self.__salario = salario

def __getnombre(self):
return self.__nombre

def __getsalario(self):
return self.__salario

def __setnombre(self, nombre):


self.__nombre = nombre

def __setsalario(self, salario):


self.__salario = salario

def __delnombre(self):
del self.__nombre

def __delsalario(self):
del self.__salario

nombre = property(fget = __getnombre,


fset = __setnombre,
fdel = __delnombre,
doc = "Soy la propiedad 'nombre'")
salario = property(fget = __getsalario,
doc = "Soy la propiedad 'salario'")

empleado1 = Empleado("Francisco José", 30000)


Poo Y TKINTER -20-

empleado1.nombre = "Rosa" # Realiza una llamada al método "fset"


print(empleado1.nombre,
empleado1.salario) # Realiza una llamada al método "fget"

La siguiente asignación no se puede realizar porque la propiedad se ha definido como de


sólo lectura. Produce una excepción del tipo AttributeError: can't set attribute

empleado1.salario = 33000

Para mostrar la documentación del objeto:

help(empleado1)

Help on Empleado in module __main__ object:

class Empleado(builtins.object)
| Methods defined here:
|
| __init__(self, nombre, salario)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| nombre
| Soy la propiedad 'nombre'
|
| salario
| Soy la propiedad 'salario'

Orden de Resolución de Métodos (MRO). El atributo especial __mro__

Es importante conocer cómo funciona la herencia en Python cuando existe una jerarquía
con varios niveles de clases que pueden tener definidos métodos que utilizan el mismo
nombre.
Poo Y TKINTER -21-
En el siguiente ejemplo se define un primer nivel de clases con una clase
llamada Clase_A. A continuación, en un segundo nivel se definen dos clases más
(Clase_A1 y Clase_A2) que heredan de la primera. Y en el tercer y último nivel, se define
la clase Clase_X que hereda de las dos clases de segundo nivel.

Teniendo en cuenta que en las clases mencionadas hay métodos con el mismo nombre,
vamos a mostrar cómo calcula Python el orden de resolución de métodos.

class Clase_A(object):
def metodo1(self):
print("Clase_A.metodo1()")

def metodo3(self):
print("Clase_A.metodo3()")

def metodo4(self):
print("Clase_A.metodo4()")

class Clase_A1(Clase_A):
def metodo1(self):
print("Clase_A1.metodo1()")

def metodo2(self):
print("Clase_A1.metodo2()")

class Clase_A2(Clase_A):
def metodo1(self):
print("Clase_A2.metodo1()")

def metodo3(self):
print("Clase_A2.metodo3()")

class Clase_X(Clase_A1, Clase_A2):


def metodo1(self):
print("Clase_X.metodo1()")

objeto1 = Clase_X() # Creación de una instancia (objeto) de Clase_X


objeto1.metodo1() # Clase_X.metodo1()
objeto1.metodo2() # Clase_A1.metodo2()
objeto1.metodo3() # Clase_A2.metodo3()
objeto1.metodo4() # Clase_A.metodo4()

En el ejemplo se crea el objeto objeto1 de la Clase_X y después se llama al


método objeto1.metodo1(). Como dicho método existe en la propia Clase_X ese será al
que se llame, con independencia de que exista en otra clase.

Como puede comprobarse el método metodo1() existe en todas las clases. Si no existiera


en la Clase_X se hubiera llamado al de la clase Clase_A1 que tiene mayor prioridad,
primero, porque se encuentra en el nivel inmediatamente anterior y, segundo, porque esa
Poo Y TKINTER -22-
clase es nombrada antes que Clase_A2 en la definición de Clase_X.

A continuación, se invoca al método objeto1.metodo2() que no existe en la


clase Clase_X pero si existe en la clase Clase_A1. Como dicho método no existe en otro
lugar, ese será el llamado.

Después, se invoca al método objeto1.metodo3() que existe tanto en Clase_A2 como


en Clase_A. Como la Clase_A2 se encuentra en el nivel inmediatamente superior (con
respecto a la Clase_X) ese será el llamado.

Por último, se llama al método objeto1.metodo4() que no existe en ninguna clase del


nivel inmediatamente superior. Como dicho método está presente en la clase Clase_A,
ese será el invocado.

La situación puede complicarse si el número de clases aumenta, si hay más niveles y


además las clases tienen ancestros diferentes.

En definitiva, dentro de una jerarquía de clases la sobrecarga se resuelve de abajo a


arriba y de izquierda a derecha. Si una clase hereda de varias clases se considerará
también el orden en que fueron declaradas en la propia definición, es decir, no es igual
definir la clase así [class Clase_X(Clase_A1, Clase_A2)] que de esta forma: [class
Clase_X(Clase_A2, Clase_A1)].

Para calcular el orden de resolución Python utiliza el método MRO (Method Resolution
Order) basado en un algoritmo llamado C3.

El cálculo realizado se puede consultar accediendo al atributo especial __mro__, que


devuelve una tupla con las clases por su orden de resolución de métodos (MRO).

print(Clase_X.__mro__)

# (class '__main__.Clase_X', class '__main__.Clase_A1',


# class '__main__.Clase_A2', class '__main__.Clase_A', class 'object')

Si cambiamos en la definición de la clase Clase_X el orden de las clases [class


Clase_X(Clase_A2, Clase_A1)] el resultado de mostrar el atributo __mro__ será:

print(Clase_X.__mro__)
# (class '__main__.Clase_X', class '__main__.Clase_A2',
# class '__main__.Clase_A1', class '__main__.Clase_A', class 'object')

La función super()

La función super() se utiliza para llamar a métodos definidos en alguna de las clases de


las que se hereda sin nombrarla/s explícitamente, teniendo en cuenta el orden de
Poo Y TKINTER -23-
resolución de métodos (MRO). No hay problemas cuando se hereda de sólo una clase,
pero si la jerarquía de clases es extensa podemos obtener resultados inesperados si no
se tiene un amplio conocimiento de todas las clases y de sus vínculos.

En el siguiente ejemplo se definen las clases Clase_I y Clase_II con dos métodos cada


una, siendo uno de ellos el método constructor o método __init__.

A continuación, se definen las clases Clase_III y Clase_IV que heredan sus métodos y


atributos de las clases Clase_I y Clase_II, pero en cada caso se han establecido con un
orden distinto en la definición.

Después, para probar el funcionamiento de la función super() se instancian dos objetos


de la Clase_III y Clase_IV, se invocan métodos y se acceden a los atributos. En el propio
código se analizan los resultados obtenidos.

class Clase_I(object):
def __init__(self):
self.var1 = 1
print('Clase_I.__init__')

def metodo1(self):
self.var2 = 1
print('Clase_I.metodo1()')

class Clase_II(object):
def __init__(self):
self.var1 = 2
print('Clase_II.__init__')

def metodo1(self):
self.var2 = 2
print('Clase_II.metodo1()')

class Clase_III(Clase_I, Clase_II):


def __init__(self):
self.var1 = 3
print('Clase_III.__init__', end = ', ')
super().__init__()

def metodo1(self):
print('Clase_III.metodo1()', end = ', ')
super().metodo1()
self.var2 = 3

class Clase_IV(Clase_II, Clase_I):


def __init__(self):
self.var1 = 4
print('Clase_IV.__init__', end = ', ')
super().__init__()
Poo Y TKINTER -24-

def metodo1(self):
print('Clase_IV.metodo1()', end = ', ')
super().metodo1()
self.var2 = 4

# Al crear objeto1 y objeto2 en el método __init__ se


# invoca también el método __init__ de su clase superior

objeto1 = Clase_III() # Clase_III.__init__, Clase_I.__init__


objeto2 = Clase_IV() # Clase_IV.__init__, Clase_II.__init__

# El atributo especial __mro__ retorna una tupla


# con las clases ordenadas de izquierda a derecha
# que indican la prioridad en la herencia.
# Mientras en la Clase_III tiene mayor prioridad en
# la herencia la Clase_I que la Clase_II; en la Clase_IV
# es al revés

print(Clase_III.__mro__)
# (class '__main__.Clase_III', class '__main__.Clase_I',
# class '__main__.Clase_II', class 'object')

print(Clase_IV.__mro__)
# (class '__main__.Clase_IV', class '__main__.Clase_II',
# class '__main__.Clase_I', class 'object')

# Al llamar al metodo1 de objeto1 y objeto2 se invoca


# también el equivalente de su clase superior

objeto1.metodo1() # Clase_III.metodo1(), Clase_I.metodo1()


objeto2.metodo1() # Clase_IV.metodo1(), Clase_II.metodo1()

# Al acceder a la variable var1 de objeto1 y objeto2


# se obtiene el valor que tiene en la clase superior
# porque en el método __init__ de su clase, DESPUÉS de
# la asignación, se invoca con la función super() al
# método __init__ de la clase superior donde se realiza
# una asignación a la misma variable.

print(objeto1.var1) # 1
print(objeto2.var1) # 2

# Al acceder a la variable var2 de objeto1 y objeto2


# se obtiene el valor que tiene en su clase
# porque aunque en el método metodo1() de su clase
# se invoca con la función super() a su equivalente de
# la clase superior, la invocación se realiza ANTES
# de la asignación a dicha variable.

print(objeto1.var2) # 3
print(objeto2.var2) # 4
Poo Y TKINTER -25-

Viernes, 25 de diciembre de 2015


Tkinter: interfaces gráficas en Python

Introducción

Con Python hay muchas posibilidades para programar una interfaz gráfica de


usuario (GUI) pero Tkinter es fácil de usar, es multiplataforma y, además, viene incluido
con Python en su versión para Windows, para Mac y para la mayoría de las distribuciones
GNU/Linux. Se le considera el estándar de facto en la programación GUI con Python.
Tkinter es un binding de la biblioteca Tcl/Tk que está también disponible para otros
lenguajes como Perl y Rubí.
A pesar de su larga historia, su uso no está demasiado extendido entre los usuarios de
equipos personales porque su integración visual con los sistemas operativos no era buena
y proporcionaba pocos widgets (controles) para construir los programas gráficos.
Sin embargo, a partir de TKinter 8.5 la situación dio un giro de ciento ochenta grados en
lo que se refiere a integración visual, mejorando en este aspecto notablemente; también
en el número de widgets que se incluyen y en la posibilidad de trabajar con estilos y
temas, que permiten ahora personalizar totalmente la estética de un programa. Por ello,
ahora Tkinter es una alternativa atractiva y tan recomendable como otras.
Este tutorial tiene como objetivo principal introducir al desarrollador que no está
familiarizado con la programación GUI en Tkinter. Para ello, seguiremos una serie de
ejemplos que muestran, de manera progresiva, el uso de los elementos que son
necesarios para construir una aplicación gráfica: ventanas, gestores de geometría,
widgets, menús, gestión de eventos, fuentes, estilos y temas. Todo a velocidad de
crucero. Para impacientes.

Consultar la versión de Tkinter


Poo Y TKINTER -26-
Normalmente, el paquete Tkinter estará disponible en nuestra instalación Python, excepto
en algunas distribuciones GNU/Linux. Para comprobar la versión de Tkinter instalada
existen varias posibilidades:
1) Iniciar el entorno interactivo de Python e introducir: import tkinter
2) tkinter.Tcl().eval('info patchlevel')
import tkinter
print(tkinter.Tcl().eval('info patchlevel'))

8.6.12
2) Si tenemos el instalador Pip introducir:
$ pip3 show tkinter

Instalar Tkinter

Si en nuestra instalación de Python, en un equipo con GNU/Linux, no se encuentra el


paquete Tkinter instalar con:$ sudo apt-get install python3-tk
(En el resto de plataformas cuando se instala Python se incluyen también los módulos de
Tkinter).
La primera aplicación con Tkinter

El siguiente ejemplo crea una aplicación que incluye una ventana con un botón en la parte
inferior. Al presionar el botón la aplicación termina su ejecución. Una ventana es el
elemento fundamental de una aplicación GUI. Es el primer objeto que se crea y sobre éste
se colocan el resto de objetos llamados widgets (etiquetas, botones, etc.).

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Las dos líneas siguientes son necesaias para hacer


Poo Y TKINTER -27-

# compatible el interfaz Tkinter con los programas basados


# en versiones anteriores a la 8.5, con las más recientes.

from tkinter import * # Carga módulo tk (widgets estándar)


from tkinter import ttk # Carga ttk (para widgets nuevos 8.5+)

# Define la ventana principal de la aplicación

raiz = Tk()

# Define las dimensiones de la ventana, que se ubicará en


# el centro de la pantalla. Si se omite esta línea la
# ventana se adaptará a los widgets que se coloquen en
# ella.

raiz.geometry('300x200') # anchura x altura

# Asigna un color de fondo a la ventana. Si se omite


# esta línea el fondo será gris

raiz.configure(bg = 'beige')

# Asigna un título a la ventana

raiz.title('Aplicación')

# Define un botón en la parte inferior de la ventana


# que cuando sea presionado hará que termine el programa.
# El primer parámetro indica el nombre de la ventana 'raiz'
# donde se ubicará el botón

ttk.Button(raiz, text='Salir', command=quit).pack(side=BOTTOM)

# Después de definir la ventana principal y un widget botón


# la siguiente línea hará que cuando se ejecute el programa
# construya y muestre la ventana, quedando a la espera de
# que alguna persona interactúe con ella.

# Si la persona presiona sobre el botón Cerrar 'X', o bien,


# sobre el botón 'Salir' el programa llegará a su fin.

raiz.mainloop()

La primera aplicación, orientada a objetos

A continuación, se muestra la misma aplicación pero orientada a objetos. Aunque este


tipo de programación siempre es recomendable con Python no es imprescindible. Sin
embargo, si vamos a trabajar con Tkinter es lo más adecuado, sobre todo, porque facilita
la gestión de los widgets y de los eventos que se producen en las aplicaciones. Desde
luego, todo van a ser ventajas.
Poo Y TKINTER -28-

Normalmente, cuando se ejecuta una aplicación gráfica ésta se queda a la espera de que
una persona interactúe con ella, que presione un botón, escriba algo en una caja de texto,
seleccione una opción de un menú, sitúe el ratón en una posición determinada, etc., o
bien, se produzca un suceso en el que no haya intervención humana como que termine
un proceso, que cambie el valor de una variable, etc. En cualquiera de estos casos, lo
habitual será vincular estos eventos o sucesos con unas acciones a realizar, que pueden
ser mejor implementadas con las técnicas propias de la programación orientada a objetos.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

# Crea una clase Python para definir el interfaz de usuario de


# la aplicación. Cuando se cree un objeto del tipo 'Aplicacion'
# se ejecutará automáticamente el método __init__() qué
# construye y muestra la ventana con todos sus widgets:

class Aplicacion():
def __init__(self):
raiz = Tk()
raiz.geometry('300x200')
raiz.configure(bg = 'beige')
raiz.title('Aplicación')
ttk.Button(raiz, text='Salir',
command=raiz.destroy).pack(side=BOTTOM)
raiz.mainloop()

# Define la función main() que es en realidad la que indica


# el comienzo del programa. Dentro de ella se crea el objeto
# aplicación 'mi_app' basado en la clase 'Aplicación':

def main():
mi_app = Aplicacion()
return 0

# Mediante el atributo __name__ tenemos acceso al nombre de un


# un módulo. Python utiliza este atributo cuando se ejecuta
# un programa para conocer si el módulo es ejecutado de forma
# independiente (en ese caso __name__ = '__main__') o es
# importado:

if __name__ == '__main__':
main()

Obtener información de una ventana


Poo Y TKINTER -29-
Para finalizar este capítulo se incluye una aplicación basada en los ejemplos anteriores
que sirve para algo, concretamente, para mostrar información relacionada con la ventana.

Para ello, en la ventana de la aplicación se han agregado nuevos widgets: un botón con la
etiqueta "Info" y una caja de texto que aparece vacía.

También, se ha incluido un método que será llamado cuando se presione el botón "Info"
para obtener la información e insertarla en la caja de texto:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

# La clase 'Aplicacion' ha crecido. En el ejemplo se incluyen


# nuevos widgets en el método constructor __init__(): Uno de
# ellos es el botón 'Info' que cuando sea presionado llamará
# al método 'verinfo' para mostrar información en el otro
# widget, una caja de texto: un evento ejecuta una acción:

class Aplicacion():
def __init__(self):

# En el ejemplo se utiliza el prefijo 'self' para


# declarar algunas variables asociadas al objeto
# ('mi_app') de la clase 'Aplicacion'. Su uso es
# imprescindible para que se pueda acceder a sus
# valores desde otros métodos:

self.raiz = Tk()
self.raiz.geometry('300x200')

# Impide que los bordes puedan desplazarse para


# ampliar o reducir el tamaño de la ventana 'self.raiz':
Poo Y TKINTER -30-

self.raiz.resizable(width=False,height=False)
self.raiz.title('Ver info')

# Define el widget Text 'self.tinfo ' en el que se


# pueden introducir varias líneas de texto:

self.tinfo = Text(self.raiz, width=40, height=10)

# Sitúa la caja de texto 'self.tinfo' en la parte


# superior de la ventana 'self.raiz':

self.tinfo.pack(side=TOP)

# Define el widget Button 'self.binfo' que llamará


# al metodo 'self.verinfo' cuando sea presionado

self.binfo = ttk.Button(self.raiz, text='Info',


command=self.verinfo)

# Coloca el botón 'self.binfo' debajo y a la izquierda


# del widget anterior

self.binfo.pack(side=LEFT)

# Define el botón 'self.bsalir'. En este caso


# cuando sea presionado, el método destruirá o
# terminará la aplicación-ventana 'self.raíz' con
# 'self.raiz.destroy'

self.bsalir = ttk.Button(self.raiz, text='Salir',


command=self.raiz.destroy)

# Coloca el botón 'self.bsalir' a la derecha del


# objeto anterior.

self.bsalir.pack(side=RIGHT)

# El foco de la aplicación se sitúa en el botón


# 'self.binfo' resaltando su borde. Si se presiona
# la barra espaciadora el botón que tiene el foco
# será pulsado. El foco puede cambiar de un widget
# a otro con la tecla tabulador [tab]

self.binfo.focus_set()
self.raiz.mainloop()

def verinfo(self):

# Borra el contenido que tenga en un momento dado


# la caja de texto

self.tinfo.delete("1.0", END)
Poo Y TKINTER -31-

# Obtiene información de la ventana 'self.raiz':

info1 = self.raiz.winfo_class()
info2 = self.raiz.winfo_geometry()
info3 = str(self.raiz.winfo_width())
info4 = str(self.raiz.winfo_height())
info5 = str(self.raiz.winfo_rootx())
info6 = str(self.raiz.winfo_rooty())
info7 = str(self.raiz.winfo_id())
info8 = self.raiz.winfo_name()
info9 = self.raiz.winfo_manager()

# Construye una cadena de texto con toda la


# información obtenida:

texto_info = "Clase de 'raiz': " + info1 + "\n"


texto_info += "Resolución y posición: " + info2 + "\n"
texto_info += "Anchura ventana: " + info3 + "\n"
texto_info += "Altura ventana: " + info4 + "\n"
texto_info += "Pos. Ventana X: " + info5 + "\n"
texto_info += "Pos. Ventana Y: " + info6 + "\n"
texto_info += "Id. de 'raiz': " + info7 + "\n"
texto_info += "Nombre objeto: " + info8 + "\n"
texto_info += "Gestor ventanas: " + info9 + "\n"

# Inserta la información en la caja de texto:

self.tinfo.insert("1.0", texto_info)

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()

En la aplicación se utiliza el método pack() para ubicar los widgets en una posición


determinada dentro de la ventana. Dicho método da nombre a uno de los tres  gestores
de geometría existentes en Tkinter, que son los responsables de esta tarea.
Poo Y TKINTER -32-
lunes, 28 de diciembre de 2015
Tkinter: Diseñando ventanas gráficas

Introducción

Para definir el modo en que deben colocarse los widgets (controles) dentro de una
ventana se utilizan los gestores de geometría. En Tkinter existen tres gestores de
geometría: pack, grid y place.

Si una aplicación tiene varias ventanas, cada una de ellas puede estar construida con
cualquiera de estos gestores, indistintamente. Será el desarrollador el que tendrá que
elegir el que mejor resuelva el diseño que tenga por delante en cada momento.

También, indicar que para construir las ventanas se pueden utilizar unos widgets
especiales (marcos, paneles, etc.) que actúan como contenedores de otros widgets. Estos
widgets se utilizan para agrupar varios controles al objeto de facilitar la operación a los
usuarios. En las ventanas que se utilicen podrá emplearse un gestor con la ventana y otro
diferente para organizar los controles dentro de estos widgets.
A continuación, vamos a conocer las características de los tres gestores geométricos y a
desarrollar una misma aplicación, utilizando cada uno de ellos.
La aplicación consta de una ventana típica de acceso a un sistema que muestra en una
caja de entrada la cuenta del usuario actual del equipo y presenta otra caja para introducir
su contraseña. En la parte inferior hay dos botones: uno con el texto 'Aceptar' para validar
la contraseña (mediante la llamada a un método) y otro con 'Cancelar' para finalizar la
aplicación. 

El gestor de geometría Pack

Con este gestor la organización de los widgets se hace teniendo en cuenta los lados de
una ventana: arriba, abajo, derecha e izquierda.
Poo Y TKINTER -33-

Si varios controles se ubican (todos) en el lado de arriba o (todos) en el lado izquierdo de


una ventana, construiremos una barra vertical o una barra horizontal de controles. Aunque
es ideal para diseños simples (barras de herramientas, cuadros de diálogos, etc.) se
puede utilizar también con diseños complejos. Además, es posible hacer que los controles
se ajusten a los cambios de tamaño de la ventana.

El ejemplo muestra la aplicación comentada con su ventana construida con el


gestor pack: 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk, font
import getpass

# Gestor de geometría (pack)

class Aplicacion():
def __init__(self):
self.raiz = Tk()
self.raiz.title("Acceso")

# Cambia el formato de la fuente actual a negrita para


# resaltar las dos etiquetas que acompañan a las cajas
# de entrada. (Para este cambio se ha importado el
# módulo 'font' al comienzo del programa):

fuente = font.Font(weight='bold')

# Define las etiquetas que acompañan a las cajas de


# entrada y asigna el formato de fuente anterior:

self.etiq1 = ttk.Label(self.raiz, text="Usuario:",


font=fuente)
self.etiq2 = ttk.Label(self.raiz, text="Contraseña:",
font=fuente)
Poo Y TKINTER -34-

# Declara dos variables de tipo cadena para contener


# el usuario y la contraseña:

self.usuario = StringVar()
self.clave = StringVar()

# Realiza una lectura del nombre de usuario que


# inició sesión en el sistema y lo asigna a la
# variable 'self.usuario' (Para capturar esta
# información se ha importando el módulo getpass
# al comienzo del programa):

self.usuario.set(getpass.getuser())

# Define dos cajas de entrada que aceptarán cadenas


# de una longitud máxima de 30 caracteres.
# A la primera de ellas 'self.ctext1' que contendrá
# el nombre del usuario, se le asigna la variable
# 'self.usuario' a la opción 'textvariable'. Cualquier
# valor que tome la variable durante la ejecución del
# programa quedará reflejada en la caja de entrada.
# En la segunda caja de entrada, la de la contraseña,
# se hace lo mismo. Además, se establece la opción
# 'show' con un "*" (asterisco) para ocultar la
# escritura de las contraseñas:

self.ctext1 = ttk.Entry(self.raiz,
textvariable=self.usuario,
width=30)
self.ctext2 = ttk.Entry(self.raiz,
textvariable=self.clave,
width=30, show="*")
self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)

# Se definen dos botones con dos métodos: El botón


# 'Aceptar' llamará al método 'self.aceptar' cuando
# sea presionado para validar la contraseña; y el botón
# 'Cancelar' finalizará la aplicación si se llega a
# presionar:

self.boton1 = ttk.Button(self.raiz, text="Aceptar",


command=self.aceptar)
self.boton2 = ttk.Button(self.raiz, text="Cancelar",
command=quit)

# Se definen las posiciones de los widgets dentro de


# la ventana. Todos los controles se van colocando
# hacia el lado de arriba, excepto, los dos últimos,
# los botones, que se situarán debajo del último 'TOP':
# el primer botón hacia el lado de la izquierda y el
# segundo a su derecha.
Poo Y TKINTER -35-

# Los valores posibles para la opción 'side' son:


# TOP (arriba), BOTTOM (abajo), LEFT (izquierda)
# y RIGHT (derecha). Si se omite, el valor será TOP
# La opción 'fill' se utiliza para indicar al gestor
# cómo expandir/reducir el widget si la ventana cambia
# de tamaño. Tiene tres posibles valores: BOTH
# (Horizontal y Verticalmente), X (Horizontalmente) e
# Y (Verticalmente). Funcionará si el valor de la opción
# 'expand' es True.
# Por último, las opciones 'padx' y 'pady' se utilizan
# para añadir espacio extra externo horizontal y/o
# vertical a los widgets para separarlos entre sí y de
# los bordes de la ventana. Hay otras equivalentes que
# añaden espacio extra interno: 'ipàdx' y 'ipady':

self.etiq1.pack(side=TOP, fill=BOTH, expand=True,


padx=5, pady=5)
self.ctext1.pack(side=TOP, fill=X, expand=True,
padx=5, pady=5)
self.etiq2.pack(side=TOP, fill=BOTH, expand=True,
padx=5, pady=5)
self.ctext2.pack(side=TOP, fill=X, expand=True,
padx=5, pady=5)
self.separ1.pack(side=TOP, fill=BOTH, expand=True,
padx=5, pady=5)
self.boton1.pack(side=LEFT, fill=BOTH, expand=True,
padx=5, pady=5)
self.boton2.pack(side=RIGHT, fill=BOTH, expand=True,
padx=5, pady=5)

# Cuando se inicia el programa se asigna el foco


# a la caja de entrada de la contraseña para que se
# pueda empezar a escribir directamente:

self.ctext2.focus_set()

self.raiz.mainloop()

# El método 'aceptar' se emplea para validar la


# contraseña introducida. Será llamado cuando se
# presione el botón 'Aceptar'. Si la contraseña
# coincide con la cadena 'tkinter' se imprimirá
# el mensaje 'Acceso permitido' y los valores
# aceptados. En caso contrario, se mostrará el
# mensaje 'Acceso denegado' y el foco volverá al
# mismo lugar.

def aceptar(self):
if self.clave.get() == 'tkinter':
print("Acceso permitido")
print("Usuario: ", self.ctext1.get())
print("Contraseña:", self.ctext2.get())
Poo Y TKINTER -36-

else:
print("Acceso denegado")

# Se inicializa la variable 'self.clave' para


# que el widget 'self.ctext2' quede limpio.
# Por último, se vuelve a asignar el foco
# a este widget para poder escribir una nueva
# contraseña.

self.clave.set("")
self.ctext2.focus_set()

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()

Como hemos comentado antes la aplicación permite cambiar la dimensión de la ventana.


Si lo hacemos los widgets se adaptarán al nuevo tamaño, teniendo en cuenta la
configuración particular de cada uno de ellos. Para comprobar el funcionamiento podemos
arrastrar los bordes de la ventana para ampliar o reducir el tamaño y comprobar como
trabaja el gestor pack:

También, para verificar como actúa la opción fill podemos cambiar el valor actual X del


widtget self.ctext1.pack por Y y arrastrar los bordes de la ventana. Si arrastramos hacia
abajo el widget se expandirá verticalmente:
Poo Y TKINTER -37-

El gestor de geometría Grid

Este gestor geométrico trata una ventana como si fuera una cuadrícula, formada por filas
y columnas como un tablero de ajedrez, donde es posible situar mediante una
coordenada (fila, columna) los widgets; teniendo en cuenta que, si se requiere, un widget
puede ocupar varias columnas y/o varias filas.

Con este gestor es posible construir ventanas complejas y hacer que los controles se
ajusten a un nuevo tamaño de las mismas. Se recomienda su uso con diseños en los que
los controles deben aparecer alineados en varias columnas o filas, es decir, siguiendo la
forma de una tabla.

Grid con ventana no dimensionable

El siguiente ejemplo pretende ilustrar cómo usar el gestor grid con una ventana no


dimensionable. También, utiliza un widget Frame con efecto 3D que contendrá al resto de
controles:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk, font
import getpass

# Gestor de geometría (grid). Ventana no dimensionable

class Aplicacion():
def __init__(self):
self.raiz = Tk()
self.raiz.title("Acceso")

# Establece que no se pueda modificar el tamaño de la


# ventana. El método resizable(0,0) es la forma abreviada
# de resizable(width=False,height=False).

self.raiz.resizable(0,0)
fuente = font.Font(weight='bold')
Poo Y TKINTER -38-

# Define un widget de tipo 'Frame' (marco) que será el


# contenedor del resto de widgets. El marco se situará
# en la ventana 'self.raiz' ocupando toda su extensión.
# El marco se define con un borde de 2 píxeles y la
# opción 'relief' con el valor 'raised' (elevado) añade
# un efecto 3D a su borde.
# La opción 'relief' permite los siguientes valores:
# FLAT (llano), RAISED (elevado), SUNKEN (hundido),
# GROOVE (hendidura) y RIDGE (borde elevado).
# La opción 'padding' añade espacio extra interior para
# que los widgets no queden pegados al borde del marco.

self.marco = ttk.Frame(self.raiz, borderwidth=2,


relief="raised", padding=(10,10))

# Define el resto de widgets pero en este caso el primer


# paràmetro indica que se situarán en el widget del
# marco anterior 'self.marco'.

self.etiq1 = ttk.Label(self.marco, text="Usuario:",


font=fuente, padding=(5,5))
self.etiq2 = ttk.Label(self.marco, text="Contraseña:",
font=fuente, padding=(5,5))

# Define variables para las opciones 'textvariable' de


# cada caja de entrada 'ttk.Entry()'.

self.usuario = StringVar()
self.clave = StringVar()
self.usuario.set(getpass.getuser())
self.ctext1 = ttk.Entry(self.marco, textvariable=self.usuario,
width=30)
self.ctext2 = ttk.Entry(self.marco, textvariable=self.clave,
show="*",
width=30)
self.separ1 = ttk.Separator(self.marco, orient=HORIZONTAL)
self.boton1 = ttk.Button(self.marco, text="Aceptar",
padding=(5,5), command=self.aceptar)
self.boton2 = ttk.Button(self.marco, text="Cancelar",
padding=(5,5), command=quit)

# Define la ubicación de cada widget en el grid.


# En este ejemplo en realidad hay dos grid (cuadrículas):
# Una cuadrícula de 1fx1c que se encuentra en la ventana
# que ocupará el Frame; y otra en el Frame de 5fx3c para
# el resto de controles.
# La primera fila y primera columna serán la número 0.
# La opción 'column' indica el número de columna y la
# opción 'row' indica el número de fila donde hay que
# colocar un widget.
# La opción 'columnspan' indica al gestor que el
Poo Y TKINTER -39-

# widget ocupará en total un número determinado de


# columnas. Las cajas para entradas 'self.ctext1' y
# 'self.ctext2' ocuparán dos columnas y la barra
# de separación 'self.separ1' tres.

self.marco.grid(column=0, row=0)
self.etiq1.grid(column=0, row=0)
self.ctext1.grid(column=1, row=0, columnspan=2)
self.etiq2.grid(column=0, row=1)
self.ctext2.grid(column=1, row=1, columnspan=2)
self.separ1.grid(column=0, row=3, columnspan=3)
self.boton1.grid(column=1, row=4)
self.boton2.grid(column=2, row=4)

# Establece el foco en la caja de entrada de la


# contraseña.

self.ctext2.focus_set()
self.raiz.mainloop()

def aceptar(self):
if self.clave.get() == 'tkinter':
print("Acceso permitido")
print("Usuario: ", self.ctext1.get())
print("Contraseña:", self.ctext2.get())
else:
print("Acceso denegado")
self.clave.set("")
self.ctext2.focus_set()

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()

Grid con ventana dimensionable


Poo Y TKINTER -40-
A continuación, la aplicación se implementa con grid con la posibilidad de adaptar los
widgets al espacio de la ventana, cuando cambie de tamaño:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk, font
import getpass

# Gestor de geometría (grid). Ventana dimensionable

class Aplicacion():
def __init__(self):
self.raiz = Tk()
self.raiz.title("Acceso")
fuente = font.Font(weight='bold')
self.marco = ttk.Frame(self.raiz, borderwidth=2,
relief="raised", padding=(10,10))
self.etiq1 = ttk.Label(self.marco, text="Usuario:",
font=fuente, padding=(5,5))
self.etiq2 = ttk.Label(self.marco, text="Contraseña:",
font=fuente, padding=(5,5))
self.usuario = StringVar()
self.clave = StringVar()
self.usuario.set(getpass.getuser())
self.ctext1 = ttk.Entry(self.marco, textvariable=self.usuario,
width=30)
self.ctext2 = ttk.Entry(self.marco, textvariable=self.clave,
show="*", width=30)
self.separ1 = ttk.Separator(self.marco, orient=HORIZONTAL)
self.boton1 = ttk.Button(self.marco, text="Aceptar",
padding=(5,5), command=self.aceptar)
self.boton2 = ttk.Button(self.marco, text="Cancelar",
padding=(5,5), command=quit)

# Para conseguir que la cuadricula y los widgets se


# adapten al contenedor, si se amplia o reduce el tamaño
# de la ventana, es necesario definir la opción 'sticky'.
# Cuando un widget se ubica en el grid se coloca en el
# centro de su celda o cuadro. Con 'sticky' se
# establece el comportamiendo 'pegajoso' que tendrá el
# widget dentro de su celda, cuando se modifique la
# dimensión de la ventana. Para ello, se utilizan para
# expresar sus valores los puntos cardinales: N (Norte),
# S (Sur), (E) Este y (W) Oeste, que incluso se pueden
# utilizar de forma combinada. El widget se quedará
# 'pegado' a los lados de su celda en las direcciones
# que se indiquen. cuando la ventana cambie de tamaño.
# Pero con definir la opción 'sticky' no es suficiente:
# hay activar esta propiedad más adelante.
Poo Y TKINTER -41-

self.marco.grid(column=0, row=0, padx=5, pady=5,


sticky=(N, S, E, W))
self.etiq1.grid(column=0, row=0,
sticky=(N, S, E, W))
self.ctext1.grid(column=1, row=0, columnspan=2,
sticky=(E, W))
self.etiq2.grid(column=0, row=1,
sticky=(N, S, E, W))
self.ctext2.grid(column=1, row=1, columnspan=2,
sticky=(E, W))
self.separ1.grid(column=0, row=3, columnspan=3, pady=5,
sticky=(N, S, E, W))
self.boton1.grid(column=1, row=4, padx=5,
sticky=(E))
self.boton2.grid(column=2, row=4, padx=5,
sticky=(W))

# A continuación, se activa la propiedad de expandirse


# o contraerse definida antes con la opción
# 'sticky' del método grid().
# La activación se hace por contenedores y por filas
# y columnas asignando un peso a la opción 'weight'.
# Esta opción asigna un peso (relativo) que se utiliza
# para distribuir el espacio adicional entre columnas
# y/o filas. Cuando se expanda la ventana, una columna
# o fila con un peso 2 crecerá dos veces más rápido
# que una columna (o fila) con peso 1. El valor
# predeterminado es 0 que significa que la columna o
# o fila no crecerá nada en absoluto.
# Lo habitual es asignar pesos a filas o columnas donde
# hay celdas con widgets.

self.raiz.columnconfigure(0, weight=1)
self.raiz.rowconfigure(0, weight=1)
self.marco.columnconfigure(0, weight=1)
self.marco.columnconfigure(1, weight=1)
self.marco.columnconfigure(2, weight=1)
self.marco.rowconfigure(0, weight=1)
self.marco.rowconfigure(1, weight=1)
self.marco.rowconfigure(4, weight=1)

# Establece el foco en la caja de entrada de la


# contraseña.

self.ctext2.focus_set()
self.raiz.mainloop()

def aceptar(self):
if self.clave.get() == 'tkinter':
print("Acceso permitido")
print("Usuario: ", self.ctext1.get())
Poo Y TKINTER -42-

print("Contraseña:", self.ctext2.get())
else:
print("Acceso denegado")
self.clave.set("")
self.ctext2.focus_set()

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()

Después de ejecutar la aplicación, si ampliamos el tamaño de la ventana podemos


comprobar como se ajustan los controles al nuevo espacio disponible según las
direcciones descritas en cada opción sticky:

También, para ver el funcionamiento de los pesos cambiaremos el peso que se asigna a
la fila 4, que es donde se encuentran los botones 'Aceptar' y 'Cancelar', con el
valor 5 para multiplicar por 5 el espacio a añadir en esta fila cuando se expanda la
ventana:
Poo Y TKINTER -43-

El gestor de geometría Place

Este gestor es el más fácil de utilizar porque se basa en el posicionamiento absoluto para
colocar los widgets, aunque el trabajo de "calcular" la posición de cada widget suele ser
bastante laborioso. Sabemos que una ventana tiene una anchura y una altura
determinadas (normalmente, medida en píxeles). Pues bien, con este método para
colocar un widget simplemente tendremos que elegir la coordenada (x,y) de su ubicación
expresada en píxeles.

La posición (x=0, y=0) se encuentra en la esquina superior-izquierda de la ventana.

Con este gestor el tamaño y la posición de un widget no cambiará al modificar las


dimensiones de una ventana.

Para finalizar, mostramos la famosa aplicación realizada con el gestor de


geometría place. En este caso el modo de mostrar el mensaje de la validación se hace
utilizando una etiqueta que cambia de color dependiendo si la contraseña es correcta o
no. También, utiliza un método adicional para "limpiar" el mensaje de error cuando se
haga clic con el ratón en la caja de entrada de la contraseña. El evento del widget se
asocia con el método utilizando el método bind().
Poo Y TKINTER -44-

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk, font
import getpass

# Gestor de geometría (place)

class Aplicacion():
def __init__(self):
self.raiz = Tk()

# Define la dimensión de la ventana

self.raiz.geometry("430x200")

# Establece que no se pueda cambiar el tamaño de la


# ventana

self.raiz.resizable(0,0)
self.raiz.title("Acceso")
self.fuente = font.Font(weight='bold')
self.etiq1 = ttk.Label(self.raiz, text="Usuario:",
font=self.fuente)
self.etiq2 = ttk.Label(self.raiz, text="Contraseña:",
font=self.fuente)

# Declara una variable de cadena que se asigna a


# la opción 'textvariable' de un widget 'Label' para
# mostrar mensajes en la ventana. Se asigna el color
# azul a la opción 'foreground' para el mensaje.

self.mensa = StringVar()
self.etiq3 = ttk.Label(self.raiz, textvariable=self.mensa,
font=self.fuente, foreground='blue')

self.usuario = StringVar()
self.clave = StringVar()
self.usuario.set(getpass.getuser())
self.ctext1 = ttk.Entry(self.raiz,
Poo Y TKINTER -45-

textvariable=self.usuario, width=30)
self.ctext2 = ttk.Entry(self.raiz,
textvariable=self.clave,
width=30,
show="*")
self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)
self.boton1 = ttk.Button(self.raiz, text="Aceptar",
padding=(5,5), command=self.aceptar)
self.boton2 = ttk.Button(self.raiz, text="Cancelar",
padding=(5,5), command=quit)

# Se definen las ubicaciones de los widgets en la


# ventana asignando los valores de las opciones 'x' e 'y'
# en píxeles.

self.etiq1.place(x=30, y=40)
self.etiq2.place(x=30, y=80)
self.etiq3.place(x=150, y=120)
self.ctext1.place(x=150, y=42)
self.ctext2.place(x=150, y=82)
self.separ1.place(x=5, y=145, bordermode=OUTSIDE,
height=10, width=420)
self.boton1.place(x=170, y=160)
self.boton2.place(x=290, y=160)
self.ctext2.focus_set()

# El método 'bind()' asocia el evento de 'hacer clic


# con el botón izquierdo del ratón en la caja de entrada
# de la contraseña' expresado con '<button-1>' con el
# método 'self.borrar_mensa' que borra el mensaje y la
# contraseña y devuelve el foco al mismo control.
# Otros ejemplos de acciones que se pueden capturar:
# <double-button-1>, <buttonrelease-1>, <enter>, <leave>,
# <focusin>, <focusout>, <return>, <shift-up>, <key-f10>,
# <key-space>, <key-print>, <keypress-h>, etc.

self.ctext2.bind('<button-1>', self.borrar_mensa)
self.raiz.mainloop()

# Declara método para validar la contraseña y mostrar


# un mensaje en la propia ventana, utilizando la etiqueta
# 'self.mensa'. Cuando la contraseña es correcta se
# asigna el color azul a la etiqueta 'self.etiq3' y
# cuando es incorrecta el color rojo. Para ello. se emplea
# el método 'configure()' que permite cambiar los valores
# de las opciones de los widgets.

def aceptar(self):
if self.clave.get() == 'tkinter':
self.etiq3.configure(foreground='blue')
self.mensa.set("Acceso permitido")
else:
Poo Y TKINTER -46-

self.etiq3.configure(foreground='red')
self.mensa.set("Acceso denegado")

# Declara un método para borrar el mensaje anterior y


# la caja de entrada de la contraseña

def borrar_mensa(self, evento):


self.clave.set("")
self.mensa.set("")

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()
lunes, 18 de enero de 2016
Tkinter: Tipos de ventanas

explicar la de la calculadora

Ventanas de aplicación y de diálogos

En la entrada anterior tratamos los distintos gestores de geometría que se utilizan para


diseñar las ventanas de una aplicación. A continuación, vamos a explorar los distintos
tipos de ventanas que podemos crear y los usos que tienen.

En Tkinter existen dos tipos de ventanas: las ventanas de aplicación, que suelen ser las
que inician y finalizan las aplicaciones gráficas; y desde las que se accede a las ventanas
de diálogo, que en conjunto constituyen la interfaz de usuario.Tanto unas como otras,
desde el punto de vista del diseño, se construyen exactamente igual con los gestores de
geometría ya conocidos: pack, grid y place.

El siguiente ejemplo muestra una ventana de aplicación con un botón 'Abrir' que cada vez
que se presione abre una ventana hija (de diálogo) diferente y en una posición diferente.
Observaremos que las ventanas hijas que se vayan generando se sitúan siempre en un
plano superior con respecto a las creadas con anterioridad. Además, si abrimos varias
Poo Y TKINTER -47-
ventanas podremos interactuar sin problemas con todas ellas, cambiar sus posiciones,
cerrarlas, etc.

En este caso, tanto para crear la ventana de aplicación como las hijas se utiliza el gestor
de geometría pack pero notaremos algunas diferencias en el código:

La ventana de aplicación se define con Tk():

self.raiz = Tk()

Las ventanas de diálogo (hijas) se definen con Toplevel():

self.dialogo = Toplevel()

El método mainloop() (bucle principal) hace que se atienda a los eventos de la ventana


de aplicación:

self.raiz.mainloop()

El método wait_window() hace que se atienda a los eventos locales de la ventana de


diálogo mientras espera a ser cerrada:

self.raiz.wait_window(self.conectar)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

class Aplicacion():
''' Clase Aplicacion '''

# Declara una variable de clase para contar ventanas

ventana = 0
Poo Y TKINTER -48-

# Declara una variable de clase para usar en el


# cálculo de la posición de una ventana

posx_y = 0

def __init__(self):
''' Construye ventana de aplicación '''

# Declara ventana de aplicación

self.raiz = Tk()

# Define dimensión de la ventana 300x200


# que se situará en la coordenada x=500,y=50

self.raiz.geometry('300x200+500+50')

self.raiz.resizable(0,0)
self.raiz.title("Ventana de aplicación")

# Define botón 'Abrir' que se utilizará para


# abrir las ventanas de diálogo. El botón
# está vinculado con el método 'self.abrir'

boton = ttk.Button(self.raiz, text='Abrir',


command=self.abrir)
boton.pack(side=BOTTOM, padx=20, pady=20)
self.raiz.mainloop()

def abrir(self):
''' Construye una ventana de diálogo '''

# Define una nueva ventana de diálogo

self.dialogo = Toplevel()

# Incrementa en 1 el contador de ventanas

Aplicacion.ventana+=1

# Recalcula posición de la ventana

Aplicacion.posx_y += 50
tamypos = '200x100+'+str(Aplicacion.posx_y)+ \
'+'+ str(Aplicacion.posx_y)
self.dialogo.geometry(tamypos)
self.dialogo.resizable(0,0)

# Obtiene identicador de la nueva ventana

ident = self.dialogo.winfo_id()
Poo Y TKINTER -49-

# Construye mensaje de la barra de título

titulo = str(Aplicacion.ventana)+": "+str(ident)


self.dialogo.title(titulo)

# Define el botón 'Cerrar' que cuando sea


# presionado cerrará (destruirá) la ventana
# 'self.dialogo' llamando al método
# 'self.dialogo.destroy'

boton = ttk.Button(self.dialogo, text='Cerrar',


command=self.dialogo.destroy)
boton.pack(side=BOTTOM, padx=20, pady=20)

# Cuando la ejecución del programa llega a este


# punto se utiliza el método wait_window() para
# esperar que la ventana 'self.dialogo' sea
# destruida.
# Mientras tanto se atiende a los eventos locales
# que se produzcan, por lo que otras partes de la
# aplicación seguirán funcionando con normalidad.
# Si hay código después de esta línea se ejecutará
# cuando la ventana 'self.dialogo' sea cerrada.

self.raiz.wait_window(self.dialogo)

def main():
mi_app = Aplicacion()
return(0)

if __name__ == '__main__':
main()

Ventanas modales y no modales

Las ventanas hijas del ejemplo anterior son del tipo no modales porque mientras existen
es posible interactuar libremente con ellas, sin ningún límite, excepto que si cerramos la
ventana principal se cerrarán todas las ventanas hijas abiertas.

Un ejemplo evidente que usa ventanas no modales está en las aplicaciones ofimáticas
más conocidas, que permiten trabajar con varios documentos al mismo tiempo, cada uno
de ellos abierto en su propia ventana, permitiendo al usuario cambiar sin restricciones de
una ventana a otra.

El caso contrario, es el de las ventanas modales. Cuando una ventana modal está


abierta no será posible interactuar con otras ventanas de la aplicación hasta que ésta sea
cerrada.

Un ejemplo típico es el de algunas ventanas de diálogo que se utilizan para establecer las
preferencias de las aplicaciones, que obligan a ser cerradas antes de permitirse la
Poo Y TKINTER -50-
apertura de otras.

Para demostrarlo, utilizamos el siguiente ejemplo en el que sólo es posible mantener


abierta sólo una ventana hija, aunque si la cerramos podremos abrir otra.

El método grab_set() se utiliza para crear la ventana modal y el método transiet() se


emplea para convertir la ventana de diálogo en ventana transitoria, haciendo que se
oculte cuando la ventana de aplicación sea minimizada.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

class Aplicacion():
ventana = 0
posx_y = 0

def __init__(self):
self.raiz = Tk()
self.raiz.geometry('300x200+500+50')
self.raiz.resizable(0,0)
self.raiz.title("Ventana de aplicación")
boton = ttk.Button(self.raiz, text='Abrir',
command=self.abrir)
boton.pack(side=BOTTOM, padx=20, pady=20)
self.raiz.mainloop()

def abrir(self):
''' Construye una ventana de diálogo '''

self.dialogo = Toplevel()
Aplicacion.ventana+=1
Aplicacion.posx_y += 50
tamypos = '200x100+'+str(Aplicacion.posx_y)+ \
'+'+ str(Aplicacion.posx_y)
Poo Y TKINTER -51-

self.dialogo.geometry(tamypos)
self.dialogo.resizable(0,0)
ident = self.dialogo.winfo_id()
titulo = str(Aplicacion.ventana)+": "+str(ident)
self.dialogo.title(titulo)
boton = ttk.Button(self.dialogo, text='Cerrar',
command=self.dialogo.destroy)
boton.pack(side=BOTTOM, padx=20, pady=20)

# Convierte la ventana 'self.dialogo' en


# transitoria con respecto a su ventana maestra
# 'self.raiz'.
# Una ventana transitoria siempre se dibuja sobre
# su maestra y se ocultará cuando la maestra sea
# minimizada. Si el argumento 'master' es
# omitido el valor, por defecto, será la ventana
# madre.

self.dialogo.transient(master=self.raiz)

# El método grab_set() asegura que no haya eventos


# de ratón o teclado que se envíen a otra ventana
# diferente a 'self.dialogo'. Se utiliza para
# crear una ventana de tipo modal que será
# necesario cerrar para poder trabajar con otra
# diferente. Con ello, también se impide que la
# misma ventana se abra varias veces.

self.dialogo.grab_set()
self.raiz.wait_window(self.dialogo)

def main():
mi_app = Aplicacion()
return(0)

if __name__ == '__main__':
main()

miércoles, 3 de febrero de 2016


Variables de control en Tkinter
https://python-para-impacientes.blogspot.com/2016/02/variables-de-control-en-tkinter.html
Poo Y TKINTER -52-

Variables de control

Las variables de control son objetos especiales que se asocian a los widgets para
almacenar sus valores y facilitar su disponibilidad en otras partes del programa. Pueden
ser de tipo numérico, de cadena y booleano.

Cuando una variable de control cambia de valor el widget que la utiliza lo refleja
automáticamente, y viceversa.

Las variables de control también se emplean para conectar varios widgets del mismo tipo,
por ejemplo, varios controles del tipo Radiobutton. En este caso tomarán un valor de
varios posibles.

Declarar variables de control

Las variables de control se declaran de forma diferente en función al tipo de dato que
almacenan:

entero = IntVar() # Declara variable de tipo entera


flotante = DoubleVar() # Declara variable de tipo flotante
cadena = StringVar() # Declara variable de tipo cadena
booleano = BooleanVar() # Declara variable de tipo booleana

También, en el momento de declarar una variable es posible asignar un valor inicial:

blog = StringVar(value="Python para impacientes")

Método set()
Poo Y TKINTER -53-
El método set() asigna un valor a una variable de control. Se utiliza para modificar el valor
o estado de un widget:

nombre = StringVar()
id_art = IntVar()
nombre.set("Python para impacientes")
id_art.set(1)
blog = ttk.Entry(ventana, textvariable=nombre, width=25)
arti = ttk.Label(ventana, textvariable=id_art)

Método get()

El método get() obtiene el valor que tenga, en un momento dado, una variable de control.


Se utiliza cuando es necesario leer el valor de un control:

print('Blog:', nombre.get())
print('Id artículo:', id_art.get())

Método trace()

El método trace() se emplea para "detectar" cuando una variable es leída, cambia de


valor o es borrada:

widget.trace(tipo, función)

El primer argumento establece el tipo de suceso a comprobar: 'r' lectura de variable, 'w'
escritura de variable y 'u' borrado de variable. El segundo argumento indica la función que
será llamada cuando se produzca el suceso.

En el siguiente ejemplo se define una variable de control de tipo cadena y con el


método trace() se asocian su lectura y cambio de valor a dos funciones que son llamadas
cuando ocurran estos sucesos. Concretamente, cuando se utilice el método set() se
llamará a la función 'cambia()' y cuando se use get() a la función 'lee()'.

def cambia(*args):
print("Ha cambiado su valor")

def lee(*args):
print("Ha sido leido su valor")

variable = StringVar()
variable.trace("w", cambia)
variable.trace("r", lee)
variable.set("Hola")
Poo Y TKINTER -54-

print(variable.get())
print(variable.get())

Estrategias para validar y calcular datos

Cuando se construye una ventana con varios widgets se pueden seguir distintas
estrategias para validar los datos que se introducen durante la ejecución de un programa:

● Una opción posible podría validar la información y realizar los cálculos después de
que sea introducida, por ejemplo, después de presionar un botón.
● Otra posibilidad podría ser haciendo uso del método trace() y de la opción
'command', para validar y calcular la información justo en el momento que un widget y su
variable asociada cambien de valor.

A continuación, se muestra un mismo caso práctico utilizando ambas técnicas.

El programa de este ejemplo calcula el coste de un viaje en tren teniendo en cuenta el


número de viajeros; el tipo de billete (de sólo ida o de ida y vuelta); la clase en la cual se
viaja (que puede ser clase turista, primera o lujo); la distancia en kilómetros y el precio por
kilómetro (por defecto es 0,10 céntimos de euro).

El cálculo del importe a pagar se realiza multiplicando número de viajeros por km y precio,
con los siguientes incrementos:

● Si el viaje es de ida y vuelta se multiplica el total por 1,5


● Si la clase es primera se multiplica el total por 1,2 y si es de lujo se multiplica por 2

Validación y cálculo posterior

En la primera solución la validación de los datos y el cálculo del importe a pagar se realiza
después presionar el botón "Calcular".
Poo Y TKINTER -55-

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

# Calcula coste de viaje con validación y cálculo posterior

class Aplicacion():
def __init__(self):
self.raiz = Tk()
self.raiz.title("Alta Velocidad")

# Declara variables de control

self.num_via = IntVar(value=1)
self.ida_vue = BooleanVar()
self.clase = StringVar(value='t')
self.km = IntVar(value=1)
self.precio = DoubleVar(value=0.10)
self.total = DoubleVar(value=0.0)

# Carga imagen para asociar a widget Label()

tren = PhotoImage(file='tren-128x64.png')

# Declara widgets de la ventana


Poo Y TKINTER -56-

# Se incluye el widget de tipo Button 'Calcular' que utiliza


# la opción 'command' para validar datos y calcular el
# importe a pagar cuando sea presionado

self.imagen1 = ttk.Label(self.raiz, image=tren,


anchor="center")
self.etiq1 = ttk.Label(self.raiz, text="Viajeros:")
self.viaje = Spinbox(self.raiz, from_=1, to=20, wrap=True,
textvariable=self.num_via,
state='readonly')
self.idavue = ttk.Checkbutton(self.raiz, text='Ida y vuelta',
variable=self.ida_vue,
onvalue=True, offvalue=False)
self.etiq2 = ttk.Label(self.raiz, text="Clase:")
self.clase1 = ttk.Radiobutton(self.raiz, text='Turista',
variable=self.clase, value='t')
self.clase2 = ttk.Radiobutton(self.raiz, text='Primera',
variable=self.clase, value='p')
self.clase3 = ttk.Radiobutton(self.raiz, text='Lujo',
variable=self.clase, value='l')
self.etiq3 = ttk.Label(self.raiz,
text="Distancia Kilómetros):")
self.dist = ttk.Entry(self.raiz, textvariable=self.km,
width=10)
self.etiq4 = ttk.Label(self.raiz, text="Precio:")
self.coste = ttk.Entry(self.raiz, textvariable=self.precio,
width=10)
self.etiq5 = ttk.Label(self.raiz, text="A Pagar (euros):")
self.etiq6 = ttk.Label(self.raiz, textvariable=self.total,
foreground="yellow", background="black",
borderwidth=5, anchor="e")
self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)

self.boton1 = ttk.Button(self.raiz, text="Calcular",


command=self.calcular)
self.boton2 = ttk.Button(self.raiz, text="Salir",
command=quit)

self.imagen1.pack(side=TOP, fill=BOTH, expand=True,


padx=10, pady=5)
self.etiq1.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.viaje.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.idavue.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq2.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.clase1.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.clase2.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
Poo Y TKINTER -57-

self.clase3.pack(side=TOP, fill=BOTH, expand=True,


padx=20, pady=5)
self.etiq3.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.dist.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq4.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.coste.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq5.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.etiq6.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.separ1.pack(side=TOP, fill=BOTH, expand=True,
padx=5, pady=5)
self.boton1.pack(side=LEFT, fill=BOTH, expand=True,
padx=10, pady=10)
self.boton2.pack(side=RIGHT, fill=BOTH, expand=True,
padx=10, pady=10)
self.raiz.mainloop()

def calcular(self):

# Función para validar datos y calcular importe a pagar

error_dato = False
total = 0
try:
km = int(self.km.get())
precio = float(self.precio.get())
except:
error_dato = True
if not error_dato:
total = self.num_via.get() * km * precio
if self.ida_vue.get():
total = total * 1.5
if self.clase.get() == 'p':
total = total * 1.2
elif self.clase.get() == 'l':
total = total * 2
self.total.set(total)
else:
self.total.set("¡ERROR!")

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()
Poo Y TKINTER -58-
Validación y cálculo inmediato

En la segunda solución no se utiliza el botón 'Calcular'. La validación y el cálculo se


realizan al cambiar el valor de cualquier widget, mostrando el resultado inmediatamente.
Para los widget de entrada de datos (Entry()) se definen dos trazas para detectar
cualquier cambio en los datos y en el resto de widgets se utiliza la opción 'command'.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *


from tkinter import ttk

# Calcula coste de viaje con validación y cálculo inmediato

class Aplicacion():
def __init__(self):
self.raiz = Tk()
self.raiz.title("Alta Velocidad")

# Declara variables de control

self.num_via = IntVar(value=1)
self.ida_vue = BooleanVar()
self.clase = StringVar(value='t')
Poo Y TKINTER -59-

self.km = IntVar(value=1)
self.precio = DoubleVar(value=0.10)
self.total = DoubleVar(value=0.0)

# Define trazas con variables de control de los widgets Entry()


# para detectar cambios en los datos. Si se producen cambios
# se llama a la función 'self.calcular' para validación y para
# calcular importe a pagar

self.km.trace('w', self.calcular)
self.precio.trace('w', self.calcular)

# Llama a función para validar y calcular

self.calcular()

# Carga imagen para asociar a widget Label()

tren = PhotoImage(file='tren-128x64.png')

# Declara widgets de la ventana


# En los widgets de tipo Spinbox, Checkbutton y Radiobutton
# se utiliza la opción 'command' para llamar a la función
# 'self.calcular' para validar datos y calcular importe a
# pagar de forma inmediata

self.imagen1 = ttk.Label(self.raiz, image=tren,


anchor="center")
self.etiq1 = ttk.Label(self.raiz, text="Viajeros:")
self.viaje = Spinbox(self.raiz, from_=1, to=20, wrap=True,
textvariable=self.num_via,
state='readonly',
command=self.calcular)
self.idavue = ttk.Checkbutton(self.raiz, text='Ida y vuelta',
variable=self.ida_vue,
onvalue=True, offvalue=False,
command=self.calcular)
self.etiq2 = ttk.Label(self.raiz, text="Clase:")
self.clase1 = ttk.Radiobutton(self.raiz, text='Turista',
variable=self.clase, value='t',
command=self.calcular)
self.clase2 = ttk.Radiobutton(self.raiz, text='Primera',
variable=self.clase, value='p',
command=self.calcular)
self.clase3 = ttk.Radiobutton(self.raiz, text='Lujo',
variable=self.clase, value='l',
command=self.calcular)
self.etiq3 = ttk.Label(self.raiz,
text="Distancia (Kilómetros):")
self.dist = ttk.Entry(self.raiz, textvariable=self.km,
width=10)
self.etiq4 = ttk.Label(self.raiz, text="Precio:")
Poo Y TKINTER -60-

self.coste = ttk.Entry(self.raiz, textvariable=self.precio,


width=10)
self.etiq5 = ttk.Label(self.raiz, text="A Pagar (euros):")
self.etiq6 = ttk.Label(self.raiz, textvariable=self.total,
foreground="yellow", background="black",
borderwidth=5, anchor="e")
self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)

self.boton1 = ttk.Button(self.raiz, text="Salir",


command=quit)

self.imagen1.pack(side=TOP, fill=BOTH, expand=True,


padx=10, pady=5)
self.etiq1.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.viaje.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.idavue.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq2.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.clase1.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.clase2.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.clase3.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.etiq3.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.dist.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq4.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.coste.pack(side=TOP, fill=X, expand=True,
padx=20, pady=5)
self.etiq5.pack(side=TOP, fill=BOTH, expand=True,
padx=10, pady=5)
self.etiq6.pack(side=TOP, fill=BOTH, expand=True,
padx=20, pady=5)
self.separ1.pack(side=TOP, fill=BOTH, expand=True,
padx=5, pady=5)
self.boton1.pack(side=RIGHT, fill=BOTH, expand=True,
padx=10, pady=10)
self.raiz.mainloop()

def calcular(self, *args):

# Función para validar datos y calcular importe a pagar

error_dato = False
total = 0
try:
Poo Y TKINTER -61-

km = int(self.km.get())
precio = float(self.precio.get())
except:
error_dato = True
if not error_dato:
total = self.num_via.get() * km * precio
if self.ida_vue.get():
total = total * 1.5
if self.clase.get() == 'p':
total = total * 1.2
elif self.clase.get() == 'l':
total = total * 2
self.total.set(total)
else:
self.total.set("¡ERROR!")

def main():
mi_app = Aplicacion()
return 0

if __name__ == '__main__':
main()

sábado, 26 de marzo de 2016


Menús, barras de herramientas y de estado en Tkinter

Introducción

Tkinter cuenta con widgets específicos para incluir menús de opciones de distintos tipos
en una aplicación. Además, siguiendo unas pautas en la construcción de ventanas es
posible agregar otros elementos como barras de herramientas y de estado.

Todos estos componentes se utilizan con frecuencia en el desarrollo de interfaces porque


facilitan mucho la interacción de los usuarios con las aplicaciones gráficas.
Poo Y TKINTER -62-

Menús de opciones

Los menús pueden construirse agrupando en una barra de menú varios submenús
desplegables, mediante menús basados en botones, o bien, utilizando los típicos menús
contextuales que cambian sus opciones disponibles dependiendo del lugar donde se
activan en la ventana de la aplicación.

Entre los tipos de opciones que se pueden incluir en un menú se encuentran aquellas que
su estado representan un valor lógico de activada o desactivada (add_checkbutton); las
que permiten elegir una opción de entre varias existentes (add_radiobutton) y las que
ejecutan directamente un método o una función (add_command).

Las opciones de un menú pueden incluir iconos y asociarse a atajos o combinaciones de


teclas que surten el mismo efecto que si éstas son seleccionadas con un clic de ratón.
También, en un momento dado, pueden deshabilitarse para impedir que puedan ser
seleccionadas.

Barras de herramientas

Una aplicación Tkinter además de menús de opciones puede incorporar barras de


herramientas construidas a base de botones apilados de manera horizontal o vertical.
Estas barras de herramientas suelen situarse, muchas veces, justo debajo de la barra de
menú de la aplicación y ejecutan los procesos más utilizados en el sistema.

Barra de estado

Las barras de estado se utilizan con asiduidad en los programas gráficos para mostrar
información que resulte útil a los usuarios. Normalmente, ocupan la última línea de la
ventana de aplicación y en algunos casos puede ocultarse para dejar más espacio
disponible a dicha ventana.

PyRemoto, un ejemplo de implementación


Poo Y TKINTER -63-

Ventana de aplicación

PyRemoto es un ejemplo que pretende mostrar cómo incluir menús y barras en una
aplicación basada en Tkinter.

La ventana principal de esta aplicación cuenta con una barra de título, una barra de menú
con tres submenús desplegables con distintos tipos de opciones, un menú contextual que
se activa haciendo clic con el botón secundario del ratón en cualquier lugar de la ventana,
una barra de herramientas con dos botones con imágenes y una barra de estado que es
posible ocultar o mostrar mediante la opción "Ver barra de estado" del submenú
"Opciones".

Menú contextual
Poo Y TKINTER -64-

Submenú "Opciones"

En el submenú "Opciones" se encuentra la opción "Guardar" que se habilitará cuando se


elija alguna opción de entre las disponibles en este submenú; y se deshabilitará cuando
se utilice la propia opción "Guardar", -prevista- para salvar las preferencias seleccionadas
por el usuario.

La aplicación de ejemplo utiliza imágenes de diferentes tamaños que se localizan en una


carpeta llamada "imagen". Estas imágenes son utilizadas en la barra de título, los
Poo Y TKINTER -65-
submenús, la barra de herramientas y en la ventana de diálogo "Acerca de" del submenú
"Ayuda". Han sido obtenidas del sitio flaticon.com que ofrece colecciones de imágenes
organizadas por distintas temáticas a programadores y a diseñadores gráficos.

Ventana de diálogo "Acerca de"

También, en el programa se incluyen varias combinaciones de teclas asociadas a


distintas opciones del menú, declaradas con el método bind().

¿Cómo funciona?

La aplicación la inicia la función main() que comprueba que todas las imágenes de la


aplicación existen en la carpeta "imagen". Si todas las imágenes están disponibles se
creará el objeto aplicación mediante la clase PyRemoto(). Esta clase cuenta con un
método __init__() que construye la ventana de la aplicación, los menús, la barra de
herramientas y una barra de estado.

La aplicación está incompleta pero enseña cómo organizar los métodos que son llamados
desde las distintas opciones de los menús y barras.

Por último, el ejemplo incluye la ventana de diálogo "Acerca de", que es de tipo modal,
para mostrar el modo de abrir este tipo de ventanas desde una opción del menú.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# name: pyremoto.py (Python 3.x).
# description: Acceso Remoto a equipos por ssh, sftp y rdp
# purpose: Construcción de menús, barras de herramientas
# y de estado
# author: python para impacientes
Poo Y TKINTER -66-

#
#------------------------------------------------------------

'''PyRemoto: Acceso Remoto a equipos por ssh, sftp y rdp '''

__author__ = 'python para impacientes'


__title__= 'PyRemoto'
__date__ = ''
__version__ = '0.0.1'
__license__ = 'GNU GPLv3'

import os, sys, webbrowser, platform


from tkinter import *
from tkinter import ttk, font, messagebox

# PYREMOTO: ACCESO REMOTO A EQUIPOS POR SSH, SFTP y RDP

class PyRemoto():
''' Clase PyRemoto '''

# DECLARAR MÉTODO CONSTRUCTOR DE LA APLICACIÓN

def __init__(self, img_carpeta, iconos):


''' Definir ventana de la aplicación, menú, submenús,
menú contextual, barra de herramientas, barra de
estado y atajos del teclado '''

# INICIALIZAR VARIABLES

self.img_carpeta = img_carpeta
self.iconos = iconos

# DEFINIR VENTANA DE LA APLICACIÓN:

self.raiz = Tk()

# ESTABLECER PROPIEDADES DE LA VENTANA DE APLICACIÓN:

self.raiz.title("PyRemoto " + __version__) # Título


self.icono1= PhotoImage(file=self.iconos[0]) # Icono app
self.raiz.iconphoto(self.raiz, self.icono1) # Asigna icono app
self.raiz.option_add("*Font", "Helvetica 12") # Fuente predeterminada
self.raiz.option_add('*tearOff', False) # Deshabilita submenús flotantes
self.raiz.attributes('-fullscreen', True) # Maximiza ventana completa
self.raiz.minsize(400,300) # Establece tamaño minimo ventana

# ESTABLECER ESTILO FUENTE PARA ALGUNOS WIDGETS:

self.fuente = font.Font(weight='normal') # normal, bold, etc...

# DECLARAR VARIABLES PARA OPCIONES PREDETERMINADAS:


# (Estos valores se podrían leer de un archivo de
Poo Y TKINTER -67-

# configuración)

self.CFG_TIPOCONEX = IntVar()
self.CFG_TIPOCONEX.set(1) # shh
self.CFG_TIPOEMUT = IntVar()
self.CFG_TIPOEMUT.set(1) # xterm
self.CFG_TIPOEXP = IntVar()
self.CFG_TIPOEXP.set(1) # thunar

# DECLARAR VARIABLE PARA MOSTRAR BARRA DE ESTADO:

self.estado = IntVar()
self.estado.set(1) # Mostrar Barra de Estado

# DEFINIR BARRA DE MENÚ DE LA APLICACION:

barramenu = Menu(self.raiz)
self.raiz['menu'] = barramenu

# DEFINIR SUBMENÚS 'Sesiones', 'Opciones' y 'Ayuda':

menu1 = Menu(barramenu)
self.menu2 = Menu(barramenu)
menu3 = Menu(barramenu)
barramenu.add_cascade(menu=menu1, label='Sesiones')
barramenu.add_cascade(menu=self.menu2, label='Opciones')
barramenu.add_cascade(menu=menu3, label='Ayuda')

# DEFINIR SUBMENÚ 'Sesiones':

icono2 = PhotoImage(file=self.iconos[1])
icono3 = PhotoImage(file=self.iconos[2])

menu1.add_command(label='Conectar...',
command=self.f_conectar,
underline=0, accelerator="Ctrl+c",
image=icono2, compound=LEFT)
menu1.add_separator() # Agrega un separador
menu1.add_command(label='Salir', command=self.f_salir,
underline=0, accelerator="Ctrl+q",
image=icono3, compound=LEFT)

# DEFINIR SUBMENÚ 'Opciones':

self.menu2.add_checkbutton(label='Barra de Estado',
variable=self.estado, onvalue=1,
offvalue=0,
command=self.f_verestado)
self.menu2.add_separator()
self.menu2.add_radiobutton(label='ssh',
variable=self.CFG_TIPOCONEX,
command=self.f_cambiaropc,
Poo Y TKINTER -68-

value=1)
self.menu2.add_radiobutton(label='sftp',
variable=self.CFG_TIPOCONEX,
command=self.f_cambiaropc,
value=2)
self.menu2.add_radiobutton(label='rdp',
variable=self.CFG_TIPOCONEX,
command=self.f_cambiaropc,
value=3)
self.menu2.add_separator()
self.menu2.add_radiobutton(label='xterm',
variable=self.CFG_TIPOEMUT,
command=self.f_cambiaropc,
value=1)
self.menu2.add_radiobutton(label='uxterm',
variable=self.CFG_TIPOEMUT,
command=self.f_cambiaropc,
value=2)
self.menu2.add_radiobutton(label='xfce4-terminal',
variable=self.CFG_TIPOEMUT,
command=self.f_cambiaropc,
value=3)
self.menu2.add_separator()
self.menu2.add_radiobutton(label='Thunar',
variable=self.CFG_TIPOEXP,
command=self.f_cambiaropc,
value=1)
self.menu2.add_radiobutton(label='Nautilus',
variable=self.CFG_TIPOEXP,
command=self.f_cambiaropc,
value=2)
self.menu2.add_separator()
self.menu2.add_command(label='Guardar',
command=self.f_opcionguardar,
state="disabled", underline=0,
accelerator="Ctrl+g")

# DEFINIR SUBMENÚ 'Ayuda':

menu3.add_command(label='Web', command=self.f_web)
menu3.add_command(label='Atajos teclado',
command=self.f_atajos)
icono4 = PhotoImage(file=self.iconos[3])
menu3.add_command(label="Acerca de",
command=self.f_acerca,
image=icono4, compound=LEFT)

# DEFINIR BARRA DE HERRAMIENTAS:

self.icono5 = PhotoImage(file=self.iconos[4])
icono6 = PhotoImage(file=self.iconos[5])
Poo Y TKINTER -69-

barraherr = Frame(self.raiz, relief=RAISED,


bd=2, bg="#E5E5E5")
bot1 = Button(barraherr, image=self.icono5,
command=self.f_conectar)
bot1.pack(side=LEFT, padx=1, pady=1)
bot2 = Button(barraherr, image=icono6,
command=self.f_salir)
bot2.pack(side=LEFT, padx=1, pady=1)
barraherr.pack(side=TOP, fill=X)

# DEFINIR BARRA DE ESTADO:


# Muestra información del equipo

info1 = platform.system()
info2 = platform.node()
info3 = platform.machine()

# Otro modo de obtener la información:


# (No disponible en algunas versiones de Windows)

#info1 = os.uname().sysname
#info2 = os.uname().nodename
#info3 = os.uname().machine

mensaje = " " + info1+ ": "+info2+" - "+info3


self.barraest = Label(self.raiz, text=mensaje,
bd=1, relief=SUNKEN, anchor=W)
self.barraest.pack(side=BOTTOM, fill=X)

# DEFINIR MENU CONTEXTUAL

self.menucontext = Menu(self.raiz, tearoff=0)


self.menucontext.add_command(label="Conectar",
command=self.f_conectar)
self.menucontext.add_command(label="Salir",
command=self.f_salir)

# DECLARAR TECLAS DE ACCESO RAPIDO:

self.raiz.bind("<Control-c>",
lambda event: self.f_conectar())
self.raiz.bind("<Control-g>",
lambda event: self.f_guardar())
self.raiz.bind("<Control-q>",
lambda event: self.f_salir())
self.raiz.bind("<Button-3>",
self.f_mostrarmenucontext)
self.raiz.mainloop()

# DECLARAR OTROS MÉTODOS DE LA APLICACIÓN:

def f_opcionguardar(self):
Poo Y TKINTER -70-

''' Si opción 'Guardar' está habilitada llama a


método para guardar opciones de configuración
de la aplicación '''

if self.menu2.entrycget(13,"state")=="normal":
self.menu2.entryconfig(13, state="disabled")
self.f_guardarconfig()

def f_guardarconfig(self):
''' Guardar opciones de configuración de la aplicación '''
print("Configuración guardada")

def f_conectar(self):
''' Definir ventana de diálogo para conectar con equipos '''
print("Conectando")

def f_cambiaropc(self):
''' Habilitar opción 'Guardar' al elegir alguna opción
de tipo de conexión, emulador de terminar o
explorador de archivos '''
self.menu2.entryconfig("Guardar", state="normal")

def f_verestado(self):
''' Ocultar o Mostrar barra de estado '''

if self.estado.get() == 0:
self.barraest.pack_forget()
else:
self.barraest.pack(side=BOTTOM, fill=X)

def f_mostrarmenucontext(self, e):


''' Mostrar menú contextual '''
self.menucontext.post(e.x_root, e.y_root)

def f_web(self):
''' Abrir página web en navegador Internet '''

pag1 = 'http://python-para-impacientes.blogspot.com/'
webbrowser.open_new_tab(pag1)

def f_atajos(self):
''' Definir ventana de diálogo con lista de
combinaciones de teclas de la aplicación '''
pass

def f_acerca(self):
''' Definir ventana de diálogo 'Acerca de' '''

acerca = Toplevel()
acerca.geometry("320x200")
acerca.resizable(width=False, height=False)
acerca.title("Acerca de")
Poo Y TKINTER -71-

marco1 = ttk.Frame(acerca, padding=(10, 10, 10, 10),


relief=RAISED)
marco1.pack(side=TOP, fill=BOTH, expand=True)
etiq1 = Label(marco1, image=self.icono5,
relief='raised')
etiq1.pack(side=TOP, padx=10, pady=10,
ipadx=10, ipady=10)
etiq2 = Label(marco1, text="PyRemoto "+__version__,
foreground='blue', font=self.fuente)
etiq2.pack(side=TOP, padx=10)
etiq3 = Label(marco1,
text="Python para impacientes")
etiq3.pack(side=TOP, padx=10)
boton1 = Button(marco1, text="Salir",
command=acerca.destroy)
boton1.pack(side=TOP, padx=10, pady=10)
boton1.focus_set()
acerca.transient(self.raiz)
self.raiz.wait_window(acerca)

def f_salir(self):
''' Salir de la aplicación '''
self.raiz.destroy()

# FUNCIONES DE LA APLICACIÓN

def f_verificar_iconos(iconos):
''' Verifica existencia de iconos

iconos -- Lista de iconos '''

for icono in iconos:


if not os.path.exists(icono):
print('Icono no encontrado:', icono)
return(1)
return(0)

def main():
''' Iniciar aplicación '''

# INICIALIZAR VARIABLES CON RUTAS

app_carpeta = os.getcwd()
img_carpeta = app_carpeta + os.sep + "imagen" + os.sep

# DECLARAR Y VERIFICAR ICONOS DE LA APLICACIÓN:

iconos = (img_carpeta + "pyremoto64x64.png",


img_carpeta + "conec16x16.png",
img_carpeta + "salir16x16.png",
img_carpeta + "star16x16.png",
img_carpeta + "conec32x32.png",
Poo Y TKINTER -72-

img_carpeta + "salir32x32.png")
error1 = f_verificar_iconos(iconos)

if not error1:
mi_app = PyRemoto(img_carpeta, iconos)
return(0)

if __name__ == '__main__':
main()

Nueva aplicaicones de python alñ 27 de mayo del 023


exec()

La función exec() permite ejecutar código Python contenido en una cadena o en un


archivo. Si el código no cumple con las reglas del lenguaje producirá una excepción. 

En el siguiente ejemplo se ejecutan varias cadenas que contienen instrucciones de


Python.
Eval

- compile() y eval():

El módulo array frente a las listas Python

Empaquetado y distribución de proyectos Python

El módulo time

Se utiliza la función timedelta que permite operar con: microseconds, milliseconds,


seconds, minutes, hours, days y weeks
Diferencia entre dos fechas en días (datetime y strptime)

Programar eventos para ejecutar tareas


Programar eventos para ejecutar después de un tiempo de espera

def contar(segundos):
Poo Y TKINTER -73-

"""Contar hasta un límite de tiempo"""


global vmax_hilos

Temporizadores

Sincronizar hilos con objetos Event

A veces

Gráficos con GooPyCharts


Crear gráficos en un cuaderno Jupyter Notebook
Instalación de Pyapng

Para construir animaciones basadas en imágenes .png sólo es necesario instalar el


módulo pyapng:

sábado, 24 de septiembre de 2016


Arte ASCII con ascii_py

Tablas con estilo con Tabulate


Tkinter: interfaces gráficas en Python

Guía Urgente de MySQL


Poo Y TKINTER -74-

https://drive.google.com/drive/folders/1SYw_tB-99DvkwR_KjLFM36YWh2hf9CVi?
usp=sharing

También podría gustarte