Glade es una herramienta de diseño de interfaces gráficas (GUI) para GTK+ que permite crear interfaces de usuario de forma visual. Glade 3 es la versión estable y ampliamente utilizada en el desarrollo de aplicaciones GTK.
Dependiendo de tu distribución Linux:
sudo apt-get install glade
sudo dnf install glade
sudo pacman -S glade
Nota: Asegúrate de tener instaladas las bibliotecas GTK+ y las dependencias de desarrollo.
El archivo generado es XML que describe la interfaz:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="ventana_principal">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Mi Ventana</property>
<property name="default_width">400</property>
<property name="default_height">300</property>
</object>
</interface>
<object class="GtkBox" id="caja_principal">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
</object>
<object class="GtkGrid" id="grid_principal">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">5</property>
<property name="column_spacing">5</property>
</object>
<object class="GtkButton" id="btn_aceptar">
<property name="label" translatable="yes">Aceptar</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<object class="GtkEntry" id="entrada_texto">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="placeholder_text" translatable="yes">Escribe algo...</property>
</object>
<object class="GtkLabel" id="etiqueta_info">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Información importante:</property>
<property name="halign">start</property>
</object>
<object class="GtkComboBox" id="combo_opciones">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="renderer_texto"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Opción 1</col>
</row>
<row>
<col id="0" translatable="yes">Opción 2</col>
</row>
</data>
</object>
<object class="GtkCheckButton" id="check_activo">
<property name="label" translatable="yes">Activar función</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
</object>
<object class="GtkWindow" id="ventana_principal">
<property name="title" translatable="yes">Ejemplo</property>
<child>
<object class="GtkBox" id="caja_principal">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="etiqueta">
<property name="label" translatable="yes">Ejemplo de diseño</property>
</object>
</child>
<child>
<object class="GtkEntry" id="entrada"/>
</child>
<child>
<object class="GtkButton" id="btn_aceptar">
<property name="label" translatable="yes">Aceptar</property>
</object>
</child>
</object>
</child>
</object>
Propiedades importantes:
Consejos para interfaces que se adaptan:
Las señales son el mecanismo de GTK para manejar eventos (clic de botón, cambio de texto, etc.).
Widget | Señal | Descripción |
---|---|---|
GtkButton | clicked | Cuando se hace clic en el botón |
GtkEntry | changed | Cuando cambia el texto |
GtkWindow | delete-event | Cuando se intenta cerrar la ventana |
GtkComboBox | changed | Cuando se cambia la selección |
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MiAplicacion:
def __init__(self):
# Cargar la interfaz
self.builder = Gtk.Builder()
self.builder.add_from_file("interfaz.glade")
# Conectar señales
self.builder.connect_signals({
"on_btn_aceptar_clicked": self.on_aceptar_clicked,
"on_ventana_principal_destroy": Gtk.main_quit
})
# Mostrar ventana
self.ventana = self.builder.get_object("ventana_principal")
self.ventana.show_all()
def on_aceptar_clicked(self, widget):
print("Botón aceptar clickeado")
if __name__ == "__main__":
app = MiAplicacion()
Gtk.main()
#include <gtk/gtk.h>
// Manejador de la señal clicked
void on_btn_aceptar_clicked(GtkButton *button, gpointer user_data) {
g_print("Botón aceptar clickeado\n");
}
int main(int argc, char *argv[]) {
GtkBuilder *builder;
GtkWidget *window;
gtk_init(&argc, &argv);
builder = gtk_builder_new();
gtk_builder_add_from_file(builder, "interfaz.glade", NULL);
// Conectar señales
gtk_builder_connect_signals(builder, NULL);
window = GTK_WIDGET(gtk_builder_get_object(builder, "ventana_principal"));
gtk_widget_show(window);
gtk_main();
return 0;
}
from gi.repository import Gtk
builder = Gtk.Builder()
builder.add_from_file("interfaz.glade")
GtkBuilder *builder = gtk_builder_new();
gtk_builder_add_from_file(builder, "interfaz.glade", NULL);
var builder = new Gtk.Builder();
builder.add_from_file("interfaz.glade");
entrada = builder.get_object("entrada_texto")
texto = entrada.get_text()
GtkEntry *entrada = GTK_ENTRY(gtk_builder_get_object(builder, "entrada_texto"));
const gchar *texto = gtk_entry_get_text(entrada);
etiqueta = builder.get_object("etiqueta_status")
etiqueta.set_text("Estado: Conectado")
GtkButton *btn = GTK_BUTTON(gtk_builder_get_object(builder, "btn_enviar"));
gtk_widget_set_sensitive(GTK_WIDGET(btn), FALSE);
<object class="GtkTreeView" id="treeview_archivos">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">liststore_archivos</property>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_nombre">
<property name="title" translatable="yes">Nombre</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_nombre"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="treeviewcolumn_tamano">
<property name="title" translatable="yes">Tamaño</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext_tamano"/>
<attributes>
<attribute name="text">1</attribute>
</attributes>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore_archivos">
<columns>
<column type="gchararray"/> <!-- Nombre -->
<column type="gchararray"/> <!-- Tamaño -->
</columns>
</object>
<object class="GtkNotebook" id="notebook_principal">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkLabel" id="label_pestana1">
<property name="visible">True</property>
<property name="label" translatable="yes">Primera pestaña</property>
</object>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<object class="GtkBox" id="contenido_pestana1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<!-- Contenido de la pestaña -->
</object>
</child>
<!-- Más pestañas... -->
</object>
<object class="GtkDialog" id="dialogo_mensaje">
<property name="title" translatable="yes">Mensaje</property>
<property name="modal">True</property>
<property name="destroy_with_parent">True</property>
<child internal-child="content_area">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkLabel" id="label_mensaje">
<property name="label" translatable="yes">¿Está seguro?</property>
</object>
</child>
</object>
</child>
<child internal-child="action_area">
<object class="GtkButtonBox">
<child>
<object class="GtkButton" id="btn_aceptar">
<property name="label" translatable="yes">Aceptar</property>
<property name="can_default">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="btn_cancelar">
<property name="label" translatable="yes">Cancelar</property>
</object>
</child>
</object>
</child>
</object>
GTK+ 3 permite estilizar widgets usando CSS:
/* Estilo para botones */
button {
padding: 5px 10px;
border-radius: 3px;
}
/* Estilo para el botón aceptar */
#btn_aceptar {
background-color: #4CAF50;
color: white;
}
/* Estilo para la ventana principal */
#ventana_principal {
background-color: #f5f5f5;
}
css_provider = Gtk.CssProvider()
css_provider.load_from_path("estilos.css")
screen = Gdk.Screen.get_default()
style_context = Gtk.StyleContext()
style_context.add_provider_for_screen(
screen, css_provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
Puedes asignar clases CSS a widgets en Glade:
.boton-destacado {
font-weight: bold;
border: 2px solid #3498db;
}
.etiqueta-error {
color: #e74c3c;
}
Para hacer tu aplicación traducible:
xgettext --from-code=UTF-8 -o messages.pot *.glade
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkWindow" id="ventana_principal">
<property name="title" translatable="yes">Editor de Texto</property>
<property name="default_width">600</property>
<property name="default_height">400</property>
<signal name="destroy" handler="on_ventana_principal_destroy"/>
<child>
<object class="GtkBox" id="caja_principal">
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menubar">
<child>
<object class="GtkMenuItem" id="menuitem_archivo">
<property name="label" translatable="yes">_Archivo</property>
<property name="use_underline">True</property>
<child>
<object class="GtkMenu" id="menu_archivo">
<child>
<object class="GtkMenuItem" id="menuitem_abrir">
<property name="label" translatable="yes">_Abrir</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_menuitem_abrir_activate"/>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem_guardar">
<property name="label" translatable="yes">_Guardar</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_menuitem_guardar_activate"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem"/>
</child>
<child>
<object class="GtkMenuItem" id="menuitem_salir">
<property name="label" translatable="yes">_Salir</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_menuitem_salir_activate"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkTextView" id="textview">
<property name="wrap_mode">word</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkStatusbar" id="statusbar"/>
</child>
</object>
</child>
</object>
</interface>
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
class EditorTexto:
def __init__(self):
self.builder = Gtk.Builder()
self.builder.add_from_file("editor.glade")
self.builder.connect_signals(self)
self.window = self.builder.get_object("ventana_principal")
self.textview = self.builder.get_object("textview")
self.statusbar = self.builder.get_object("statusbar")
self.window.show_all()
def on_ventana_principal_destroy(self, widget):
Gtk.main_quit()
def on_menuitem_abrir_activate(self, widget):
dialog = Gtk.FileChooserDialog(
title="Abrir archivo",
parent=self.window,
action=Gtk.FileChooserAction.OPEN
)
dialog.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK
)
response = dialog.run()
if response == Gtk.ResponseType.OK:
try:
with open(dialog.get_filename(), 'r') as f:
buffer = self.textview.get_buffer()
buffer.set_text(f.read())
except Exception as e:
print("Error al abrir:", e)
dialog.destroy()
def on_menuitem_guardar_activate(self, widget):
dialog = Gtk.FileChooserDialog(
title="Guardar archivo",
parent=self.window,
action=Gtk.FileChooserAction.SAVE
)
dialog.add_buttons(
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK
)
response = dialog.run()
if response == Gtk.ResponseType.OK:
try:
buffer = self.textview.get_buffer()
start, end = buffer.get_bounds()
text = buffer.get_text(start, end, False)
with open(dialog.get_filename(), 'w') as f:
f.write(text)
except Exception as e:
print("Error al guardar:", e)
dialog.destroy()
def on_menuitem_salir_activate(self, widget):
self.window.destroy()
if __name__ == "__main__":
app = EditorTexto()
Gtk.main()