16.7. El gestor de Interfaces de Usuario UIManager

16.7.1. Perspectiva general

UIManager proporciona una manera de crear menús y barras de herramientas a partir de una descripción similar a XML. UIManager usa objetos ActionGroup para gestionar los objetos Action que proporcionan la estructura de control de los elementos del menú y la barra de herramientas.

Al usar el gestor UIManager se pueden introducir y eliminar dinámicamente múltiples acciones y descripciones de la Interfaz de Usuario. Ello permite modificar los menús y las barras de herramientas con los cambios de modo de la aplicación (por ejemplo, si cambia de edición de texto a edición de imágenes), o en el caso de que se añadan o eliminen características accesorias de la aplicación.

Se puede usar UIManager para crear los menús y barras de herramientas de la interfaz de usuario de una aplicación así:

  • Se crear una instancia de UIManager
  • Se extrae el grupo de atajos AccelGroup del UIManager y se añade a la ventana de nivel superior
  • Se crean las instancias de Grupo de Acciones (ActionGroup) y se rellenan pertinentemente con instancias de acciones Action.
  • Se añaden las instancias de ActionGroup al UIManager en el orden en el que se desee que aparezcan las instancias de acciones (Action).
  • Se añaden las descripciones XML de la interfaz al gestor UIManager. Es necesario asegurarse de que todas las acciones Action referenciadas por las descripciones XML están disponibles en las instancias de los grupos ActionGroup del UIManager.
  • Se extraen, por nombre, las referencias a los controles de barra de menús, menú y barra de herramientas para utilizarlas en la construcción de la interfaz de usuario.
  • Se modifica dinámicamente la interfaz de usuario añadiendo y eliminando las descripciones de Interfaz de Usuario y añadiendo, reordenando y eliminando las instancias asociadas de grupos de acciones (ActionGroup).

16.7.2. Creación de un gestor UIManager

Las instancias de UIManager se crean con el constructor:

  uimamager = gtk.UIManager()

Los nuevos UIManager se crean con un grupo de atajos (AccelGroup) asociado, que puede obtenerse con el método:

  accelgroup = uimanager.get_accel_group()

El grupo de atajos (AccelGroup) debería añadirse a la ventana de nivel superior de la aplicación para que las usuarias de la aplicación puedan usar los atajos de la acción (Action). Por ejemplo:

  window = gtk.Window()
  ...
  uimanager = gtk.UIManager()
  accelgroup = uimanager.get_accel_group()
  window.add_accel_group(accelgroup)

16.7.3. Adición y Eliminación de Grupos de Acciones (ActionGroups)

Tal como se describe en Sección 16.1.2, “Grupos de Acciones (ActionGroups)”, los grupos de acciones ActionGroups pueden llenarse con acciones (Actions) utilizando los métodos auxiliares add_actions(), add_toggle_actions() y add_radio_actions(). Los grupos de acciones (ActionGroup) se pueden usar desde un gestor UIManager una vez que han sido añadidos a su lista de grupo de acciones (ActionGroup) con el método:

  uimanager.insert_action_group(action_group, pos)

donde pos es el índice de la posición en la que debería insertarse action_group. Un gestor de interfaces UIManager puede contener varios grupos de acciones (ActionGroups) con nombres de acciones (Action) repetidos. Por ello es importante el orden de los objetos ActionGroup, puesto que la búsqueda de una acción (Action) finaliza cuando se encuentra la primera acción (Action) con el nombre dado. Ello implica que las acciones que están en objetos ActionGroup que están situados antes ocultan a los que están colocados después.

Las acciones referenciadas en una descripción XML de la interfaz de usuario deben añadirse al UIManager antes que la propia descripción.

Se puede eliminar un grupo de acciones ActionGroup de un gestor de interfaz de usuario UIManager con el método:

  uimanager.remove_action_group(action_group)

Se puede obtener la lista de los objetos ActionGroup asociados con un determinado UIManager con el método:

  actiongrouplist = uimanager.get_action_groups()

16.7.4. Descripciones de la Interfaz de Usuario

Las descripciones de la Interfaz de Usuario aceptadas por UIManager son simples definiciones XML con los siguientes elementos:

ui

El elemento raíz de una descripción de una Interfaz de Usuario. Se puede omitir. Puede contener elementos menubar, popup, toolbar y accelerator.

menubar

Elemento de nivel superior que describe una estructura de barra de menú (MenuBar) y puede contener elementos MenuItem, separator, placeholder y menu. Tiene un atributo opcional name (nombre) que, si se omite, toma el valor "menubar".

popup

Elemento de nivel superior que describe una estructura de menú emergente (Menu) y que puede contener elementos menuitem, separator, placeholder, y menu. Tiene un atributo opcional name (nombre) que, si se omite, toma el valor "popup".

toolbar

Elemento de nivel superior que describe una estructura de barra de herramientas (Toolbar) y que puede contener otros elementos toolitem, separator y placeholder. Posee un atributo opcional name (nombre) que, si se omite, toma el valor "toolbar".

placeholder

Elemento que identifica una posición dentro de un menubar, toolbar, popup o menu. Este elemento puede contener otros elementos menuitem, separator, placeholder, y menu. Los elementos Placeholder se usan al incorporar descripciones de Interfaz de Usuario para permitir, por ejemplo, la construcción de un menú a partir de descripciones de Interfaz de usuario utilizando nombres de contenedores placeholder compartidos. Posee un atributo opcional name (nombre), que, si se omite, toma el valor "placeholder".

menu

Elemento que describe una estructura de menú Menu y puede contener otros elementos menuitem, separator, placeholder y menu. Un elemento menu tiene un atributo obligatorio action que denomina un objeto de acción Action que se usará en la creación del Menu. Opcionalmente puede incluir los atributos name (nombre) y position (posición). Si no se especifica name se usa el valor correspondiente al elemento action como nombre. El atributo position puede tomar el valor "top" (superior) o "bottom" (inferior), usándose éste último si no se especifica position.

menuitem

Elemento que escribe un elemento de menú MenuItem. El elemento menuitem posee un atributo obligatorio action que denomina un objeto de acción Action que se usa para crear el elemento de menú MenuItem. También posee los atributos optativos name (nombre) y position (posición). Si no se indica name entonces se usa el nombre correspondiente al elemento action. El atributo position puede tomar el valor "top" (superior) o el valor "bottom" (inferior), usándose éste último si no se especifica position.

toolitem

Elemento que describe un elemento de barra de herramientas ToolItem. El elemento toolitem posee un atributo obligatorio action que denomina un objeto de acción Action que se usa para crear la barra de herramientas Toolbar. También posse los atributos optativos name (nombre) y position (posición). Si no se indica name entonces se usa el nombre correspondiente al elemento action. El atributo position puede tomar el valor "top" (superior) o el valor "bottom" (inferior), usándose éste último si no se especifica position.

separator

Elemento que describe un separador de elemento de menú SeparatorMenuItem o un separador de elemento de barra de herramientas SeparatorToolItem según corresponda.

accelerator

Elemento que describe un atajo de teclado (acelerador). El elemento accelerator posee un atributo obligatorio action que denomina un objeto de acción Action que define la combinación de teclas del atajo y que se activa con el atajo. También tiene un atributo opcional de nombre name. Si no se especifica name se usa el nombre de action como nombre.

Como ejemplo, una descripción de Interfaz de Usuario que se podría usar para la creación de una interfaz similar a la de Figura 16.4, “Ejemplo de ActionGroup” es:

  <ui>
    <menubar name="MenuBar">
      <menu action="File">
        <menuitem action="Quit"/>
      </menu>
      <menu action="Sound">
        <menuitem action="Mute"/>
      </menu>
      <menu action="RadioBand">
        <menuitem action="AM"/>
        <menuitem action="FM"/>
        <menuitem action="SSB"/>
      </menu>
    </menubar>
    <toolbar name="Toolbar">
      <toolitem action="Quit"/>
      <separator/>
      <toolitem action="Mute"/>
      <separator name="sep1"/>
      <placeholder name="RadioBandItems">
        <toolitem action="AM"/>
        <toolitem action="FM"/>
        <toolitem action="SSB"/>
      </placeholder>
    </toolbar>
  </ui>

Obsérvese que esta descripción simplemente usa los nombres de los elementos action como nombres de la mayoría de los elementos, en lugar de especificar sus atributos name. También es desaconsejable el uso del elemento ui puesto que parece innecesario.

La jerarquía de objetos que se crea al usar una descripción de Interfaz de Usuario es muy parecida a la jerarquía de elementos XML exceptuando que los elementos contenedores placeholder se introducen en sus elementos padre.

Se puede acceder a un control de la jerarquía creada por una descripción de Interfaz de Usuario mediante su ruta, que se compone del nombre del elemento de control y sus elementos anteriores separados por barras inclinadas ("/"). Por ejemplo, si se usa la descripción anterior, estas serían rutas válidas de algunos controles:

  /MenuBar
  /MenuBar/File/Quit
  /MenuBar/RadioBand/SSB
  /Toolbar/Mute
  /Toolbar/RadioBandItems/FM

Hay que observar que el nombre de los contenedores placeholder deben incluirse en la ruta. Generalmente simplemente se accederá a los controles de nivel superior (por ejemplo, "/MenuBar" y "/Toolbar") pero es posible que sea necesario acceder a otros controles de nivel inferior para, por ejemplo, cambiar una propiedad.

16.7.5. Adición y Eliminación de Descripciones de Interfaz de Usuario

Una vez que se configura un gestor de Interfaz de Usuario UIManager con un grupo de acciones ActionGroup entonces es posible añadir una descripción de Interfaz de Usuario e integrarla con la interfaz existente mediante los siguientes métodos:

  merge_id = uimanager.add_ui_from_string(buffer)

  merge_id = uimanager.add_ui_from_file(filename)

donde buffer es una cadena que contiene una descripción de Interfaz de Usuario y filename es el archivo que contiene una descripción de Interfaz de Usuario. Ambos métodos devuelven un identificador merge_id que consiste en un valor entero único. Si falla el método se emite la excepción GError. El identificador merge_id puede usarse para eliminar la descripción de Interfaz de Usuario del gestor UIManager con el método:

  uimanager.remove_ui(merge_id)

Los mismos métodos se pueden usar más de una vez para añadir descripciones adicionales, que se incorporarán para obtener una descripción XML de Interfaz de Usuario combinada. Se hablará más de las Interfaces de Usuario combinadas de forma más detallada en la sección Sección 16.7.8, “Combinación de Descripciones de Interfaz de Usuario”.

Se puede añadir un elemento sencillo de Interfaz de Usuario a la descripción existente con el método:

  uimanager.add_ui(merge_id, path, name, action, type, top)

donde merge_id es un valor entero único, path es la ruta donde se añadirá el nuevo elemento, action es el nombre de una acción Action o None para añadir un elemento separator, type es el tipo de elemento que se ha de añadir y top (superior) es un valor booleano. Si top es TRUE el elemento se añadirá antes que sus elementos hermanos, y después en caso contrario.

merge_id se obtendría con el método:

  merge_id = uimanager.new_merge_id()

Los valores enteros devueltos por el método new_merge_id() son monótonamente crecientes.

path (ruta) es una cadena compuesta por el nombre del elemento y los nombres de sus ancestros unidos por una barra inclinada ("/") pero sin incluir el nudo optativo raíz "/ui". Por ejemplo, "/MenuBar/RadioBand" es la ruta del elemento menu llamado "RadioBand" de la siguiente descripción de Interfaz de Usuario:

  <menubar name="MenuBar">
    <menu action="RadioBand">
    </menu>
  </menubar>

El valor de type (tipo) debe ser uno de los siguientes:

gtk.UI_MANAGER_AUTO

El tipo del elemento de Interfaz de Usuario (menuitem, toolitem o separator) se establece en función del contexto.

gtk.UI_MANAGER_MENUBAR

Una barra de menú.

gtk.UI_MANAGER_MENU

Un menú.

gtk.UI_MANAGER_TOOLBAR

Una barra de herramientas.

gtk.UI_MANAGER_PLACEHOLDER

Un contenedor (placeholder).

gtk.UI_MANAGER_POPUP

Un menú emergente.

gtk.UI_MANAGER_MENUITEM

Un elemento de menú.

gtk.UI_MANAGER_TOOLITEM

Un elemento de barra de herramientas.

gtk.UI_MANAGER_SEPARATOR

Un separador.

gtk.UI_MANAGER_ACCELERATOR

Un atajo o acelerador.

add_ui() falla sin aviso si el elemento no es añadido. El uso de add_ui() es de un nivel tan bajo que se deberían usar siempre en su lugar los métodos auxiliares add_ui_from_string() y add_ui_from_file().

La adición de un elemento o una descripción de Interfaz de Usuario provoca la actualización de la jerarquía de controles en una función ociosa (idle). Se puede asegurar que la jerarquía de controles se ha actualizado antes de acceder a ella utilizando el método:

  uimanager.ensure_update()

16.7.6. Acceso a los Controles de la Interfaz de Usuario

Se accede a un control de la jerarquía de controles de la Interfaz de Usuario mediante el método:

  widget = uimanager.get_widget(path)

donde path (ruta) es una cadena que contiene el nombre del elemento control y sus ancestros de la forma que se describe en Sección 16.7.4, “Descripciones de la Interfaz de Usuario”.

Por ejemplo, dada la siguiente descripción de Interfaz de Usuario:

  <menubar name="MenuBar">
    <menu action="File">
      <menuitem action="Quit"/>
    </menu>
    <menu action="Sound">
      <menuitem action="Mute"/>
    </menu>
    <menu action="RadioBand">
      <menuitem action="AM"/>
      <menuitem action="FM"/>
      <menuitem action="SSB"/>
    </menu>
  </menubar>
  <toolbar name="Toolbar">
    <toolitem action="Quit"/>
    <separator/>
    <toolitem action="Mute"/>
    <separator name="sep1"/>
    <placeholder name="RadioBandItems">
      <toolitem action="AM"/>
      <toolitem action="FM"/>
      <toolitem action="SSB"/>
    </placeholder>
  </toolbar>

que haya sido añadida al gestor UIManager uimanager, es posible acceder a la barra de menús (MenuBar) y barra de herramientas (Toolbar) para su uso en una ventana de aplicación (Window) utilizando el código que sigue:

  window = gtk.Window()
  vbox = gtk.VBox()
  menubar = uimanager.get_widget('/MenuBar')
  toolbar = uimanager.get_widget('/Toolbar')
  vbox.pack_start(meunbar, False)
  vbox.pack_start(toolbar, False)

De la misma forma, se accede a los controles de los niveles inferiores mediante sus rutas. Por ejemplo, se accede al elemento de la clase RadioToolButton llamado "SSB" así:

  ssb = uimanager.get_widget('/Toolbar/RadioBandItems/SSB')

Para facilitar las cosas, se pueden obtener todos los controles de nivel superior de un determinado tipo mediante el método:

  toplevels = uimanager.get_toplevels(type)

donde type especifica el tipo de los controles que se devolverán usando una combinación de las banderas: gtk.UI_MANAGER_MENUBAR, gtk.UI_MANAGER_TOOLBAR y gtk.UI_MANAGER_POPUP. Se puede usar el método gtk.Widget.get_name() para determinar qué control de nivel superior se tiene.

Se puede obtener la acción Action que usa el control auxiliar asociado con un elemento de Interfaz de Usuario usando el método:

  action = uimanager_get_action(path)

donde path (ruta) es una cadena que contiene la ruta a un elemento de la Interfaz de Usuario de uimanager. Si el elemento no posee una acción Action asociada entonces se devuelve None.

16.7.7. Ejemplo sencillo de Gestor de Interfaz UIManager

uimanager.py es un ejemplo sencillo de programa que ilustra el uso de un gestor de interfaz de usuario UIManager. Figura 16.13, “Programa sencillo de Gestor de Interfaz de Usuario UIManager” muestra el programa en funcionamiento.

Figura 16.13. Programa sencillo de Gestor de Interfaz de Usuario UIManager

Programa sencillo de Gestor de Interfaz de Usuario UIManager

El programa de ejemplo uimanager.py usa la descripción XML de Sección 16.7.6, “Acceso a los Controles de la Interfaz de Usuario”. El texto de las dos etiquetas se cambian como respuesta a la activación de la acción biestado (ToggleAction) "Mute" y de las acciones de exclusión mútua (RadioAction) "AM", "FM" and "SSB". Todas las acciones están contenidas en un único grupo de acciones (ActionGroup) que permite que se pueda conmutar la sensibilidad y visibilidad de todos los controles auxiliares de las acciones mediante los botones biestado "Sensitive" y "Visible". El uso del elemento contenedor placeholder se describe posteriormente en Sección 16.7.8, “Combinación de Descripciones de Interfaz de Usuario”.

16.7.8. Combinación de Descripciones de Interfaz de Usuario

La combinación de descripciones de Interfaz de Usuario se hace en función del nombre de los elementos XML. Tal como se indicó más arriba, los elementos individuales de la jerarquía pueden ser accedidos usando un nombre de ruta que está formado por el nombre del elemento y los nombres de sus ancestros. Por ejemplo, si usamos la descripción de Interfaz de Usuario de Sección 16.7.4, “Descripciones de la Interfaz de Usuario”, el elemento toolitem "AM" tiene la ruta "/Toolbar/RadioBandItems/AM" mientras que el elemento menuitem "FM" tiene la ruta "/MenuBar/RadioBand/FM".

Si se combina una descripción de Interfaz de Usuario con esa descripción de Interfaz entonces sus elementos se añaden como elementos del mismo nivel que los elementos existentes. Por ejemplo, si la descripción de Interfaz de Usuario:

  <menubar name="MenuBar">
    <menu action="File">
      <menuitem action="Save" position="top"/>
      <menuitem action="New" position="top"/>
    </menu>
    <menu action="Sound">
      <menuitem action="Loudness"/>
    </menu>
    <menu action="RadioBand">
      <menuitem action="CB"/>
      <menuitem action="Shortwave"/>
    </menu>
  </menubar>
  <toolbar name="Toolbar">
    <toolitem action="Save" position="top"/>
    <toolitem action="New" position="top"/>
    <separator/>
    <toolitem action="Loudness"/>
    <separator/>
    <placeholder name="RadioBandItems">
      <toolitem action="CB"/>
      <toolitem action="Shortwave"/>
    </placeholder>
  </toolbar>

se añade a nuestra descripción de Interfaz de Usuario de ejemplo:

  <menubar name="MenuBar">
    <menu action="File">
      <menuitem action="Quit"/>
    </menu>
    <menu action="Sound">
      <menuitem action="Mute"/>
    </menu>
    <menu action="RadioBand">
      <menuitem action="AM"/>
      <menuitem action="FM"/>
      <menuitem action="SSB"/>
    </menu>
  </menubar>
  <toolbar name="Toolbar">
    <toolitem action="Quit"/>
    <separator/>
    <toolitem action="Mute"/>
    <separator name="sep1"/>
    <placeholder name="RadioBandItems">
      <toolitem action="AM"/>
      <toolitem action="FM"/>
      <toolitem action="SSB"/>
    </placeholder>
  </toolbar>

se crearía la siguiente descripción de Interfaz de Usuario combinada:

  <menubar name="MenuBar">
    <menu name="File" action="File">
      <menuitem name="New" action="New"/>
      <menuitem name="Save" action="Save"/>
      <menuitem name="Quit" action="Quit"/>
    </menu>
    <menu name="Sound" action="Sound">
      <menuitem name="Mute" action="Mute"/>
      <menuitem name="Loudness" action="Loudness"/>
    </menu>
    <menu name="RadioBand" action="RadioBand">
      <menuitem name="AM" action="AM"/>
      <menuitem name="FM" action="FM"/>
      <menuitem name="SSB" action="SSB"/>
      <menuitem name="CB" action="CB"/>
      <menuitem name="Shortwave" action="Shortwave"/>
    </menu>
  </menubar>
  <toolbar name="Toolbar">
    <toolitem name="New" action="New"/>
    <toolitem name="Save" action="Save"/>
    <toolitem name="Quit" action="Quit"/>
    <separator/>
    <toolitem name="Mute" action="Mute"/>
    <separator name="sep1"/>
    <placeholder name="RadioBandItems">
      <toolitem name="AM" action="AM"/>
      <toolitem name="FM" action="FM"/>
      <toolitem name="SSB" action="SSB"/>
      <toolitem name="CB" action="CB"/>
      <toolitem name="Shortwave" action="Shortwave"/>
    </placeholder>
    <separator/>
    <toolitem name="Loudness" action="Loudness"/>
    <separator/>
  </toolbar>

Si se examina el XML resultante se puede ver que los elemnentos menuitem "New" y "Save" se han combinado antes que el elemento "Quit" como resultado de haber fijado el atributo "position" a "top", que significa que el elemento debe ser antepuesto. De la misma forma, los elementos toolitem "New" y "Save" se han antepuesto a "Toolbar". Obsérvese que los elementos "New" y "Save" son invertidos en el proceso de combinación.

El elemento toolitem "Loudness" se añade tras los elementos "Toolbar" y aparece el último en la descripción combinada de la Interfaz de Usuario, aunque no sea así en su propia descripción de Interfaz de Usuario. El elemento placeholder "RadioBandItems" en ambas Interfaces de Usuario combina los elementos toolitem "CB" y "Shortwave" con los elementos "AM", "FM", and "SSB". Si no se hubiese usado el elemento placeholder "RadioBandItems" entonces los elementos "CB" y "Shortwave" se habrían situado tras el elemento "Loudness".

Se puede obtener una representación de la descripción de Interfaz de Usuario utilizada por un gestor UIManager con el método:

  uidesc = uimanager.get_ui()

El programa de ejemplo uimerge.py muestra la combinación de las descripciones de Interfaz de Usuario anteriores. Figura 16.14, “Ejemplo UIMerge” ilustra la Interfaz original y la combinada:

Figura 16.14. Ejemplo UIMerge

Ejemplo UIMerge

El programa de ejemplo usa tres objetos ActionGroup:

  • Objetos Action para los menús "File", "Sound" y "Radio Band"
  • Objetos Action para los menús "Quit", "Mute", "AM", "FM", "SSB" y "Radio Band"
  • Objetos Action para los elementos "Loudness", "CB" y "Shortwave"

Los controles de botón biestado (ToggleButton) "Sensitive" y Visible" controlan la sensibilidad y visibilidad de únicamente el segundo grupo de acciones (ActionGroup).

16.7.9. Señales de UIManager

UIManager posee una par de señales interesantes a las que se puede conectar una aplicación. La señal "actions-changed" se emite cuando se añade o elimina un grupo de acciones ActionGroup de un gestor UIManager. La signatura de la retrollamada es:

  def callback(uimanager, ...)

La señal "add-widget" se emite cuando se crea un control auxiliar MenuBar o Toolbar. La signatura de la retrollamada es:

  def callback(uimanager, widget, ...)

donde widget es el control recién creado.