Inicio > C/C++, Glib, Gtk > ListView con búsqueda rápida en GTKMM

ListView con búsqueda rápida en GTKMM

Es muy útil cuando listamos elementos en una aplicación proporcionar al usuario una opción de búsqueda rápida. Hacemos la aplicación más amigable con poco esfuerzo extra por nuestra parte.

El ejemplo que traigo es muy parecido al ejemplo de ListStore, aunque añado la posibilidad de filtrar la lista con el texto introducido en un widget Entry.

La clave está en crear una columna más en el modelo de datos (c_visible) que determinará si el elemento es visible o no en la lista, y en lugar de hacer que nuestro TreeView siga el modelo por defecto, haremos que siga un modelo filtrado.

Por otra parte, cada vez que escribamos algo en nuestro widget Entry, recorreremos las filas de la lista para ver qué se oculta y qué se muestra.

Veamos el código:

tvsmc.h – Será el modelo de datos

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* @(#)tvsmc.h
 */


#ifndef _TVSMC_H
#define _TVSMC_H 1

#include <gtkmm.h>

using namespace Gtk;

class TVSModelColumns : public TreeModel::ColumnRecord
{
 public:
  TVSModelColumns();

  TreeModelColumn<unsigned> c_id;
  TreeModelColumn<Glib::ustring> c_name;
  TreeModelColumn<Glib::ustring> c_description;
  TreeModelColumn<bool> c_visible;
};

#endif /* _TVSMC_H */

tvsmc.cpp

1
2
3
4
5
6
7
8
9
#include "tvsmc.h"

TVSModelColumns::TVSModelColumns()
{
  add(c_id);
  add(c_name);
  add(c_description);
  add(c_visible);
}

tvsexample.h – La ventana principal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* @(#)tvsexample.h
 */


#ifndef _TVSEXAMPLE_H
#define _TVSEXAMPLE_H 1

#include <gtkmm.h>
#include "tvsmc.h"

using namespace Gtk;

class TVSExample : public Window
{
 public:
  TVSExample();
  virtual ~TVSExample();

 protected:
  /* Events */
  void onButtonQuit();
  bool reallyQuitQuestion();
  bool onCloseButton(GdkEventAny *event);
  bool onKeyRelease(GdkEventKey *event);

  /* Other method */
  void dataFill();
  void insertRow(unsigned id, Glib::ustring name, Glib::ustring description);
  void showAllRows();

  /* Data model */
  TVSModelColumns columns;
  Glib::RefPtr<Gtk::ListStore> refTreeModel;
  Glib::RefPtr<Gtk::TreeModelFilter> refFilter;

  /* Widgets */
  Box w_vBox;
  ScrolledWindow w_scroll;
  TreeView w_treeview;
  ButtonBox w_buttonBox;
  Button w_button;
  Box w_hBox;
  Label w_entryLabel;
  Entry w_textEntry;
};

#endif /* _TVSEXAMPLE_H */

tvsexample.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include "tvsexample.h"
#include <iostream>

using namespace std;

TVSExample::TVSExample(): w_vBox(ORIENTATION_VERTICAL),
              w_button("Quit"),
              w_hBox(ORIENTATION_HORIZONTAL),
              w_entryLabel("Search: ")
{
  this->set_title("TreeView with fast search");
  this->set_default_size(400,400);
  // Adds vertical box
  this->add(w_vBox);

  // Adds widgets to the box
  w_vBox.pack_start(w_hBox, PACK_SHRINK);
  w_vBox.pack_start(w_scroll);
  w_vBox.pack_start(w_buttonBox, PACK_SHRINK);

  // Sets widgets for search box
  w_hBox.pack_start(w_entryLabel);
  w_hBox.pack_start(w_textEntry);

  // Sets widgets form scrolled window
  w_scroll.add(w_treeview);

  // Configure buttonbox
  w_buttonBox.pack_start(w_button);
  w_buttonBox.set_border_width(5);
  w_buttonBox.set_layout(BUTTONBOX_END);

  // Events
  w_button.signal_clicked().connect(sigc::mem_fun(*this, &TVSExample::onButtonQuit));
  this->signal_delete_event().connect(sigc::mem_fun(*this, &TVSExample::onCloseButton));
  w_textEntry.signal_key_release_event().connect(sigc::mem_fun(*this, &TVSExample::onKeyRelease));

  // Create the model
  refTreeModel = ListStore::create(columns);
  refFilter = TreeModelFilter::create(refTreeModel);
  refFilter->set_visible_column(columns.c_visible);
  w_treeview.set_model(refFilter);

  // Makes the treeview show some columns
  w_treeview.append_column("Id", columns.c_id);
  w_treeview.append_column("Name", columns.c_name);
  w_treeview.append_column("Description", columns.c_description);
  // Don't add c_visible

  // Fill treeview with data
  dataFill();

  // Show everything
  this->show_all_children();
}

TVSExample::~TVSExample()
{
}

void TVSExample::onButtonQuit()
{
  cout << "Quit button pressed"<<endl;

  if (reallyQuitQuestion())
    this->hide();
}

bool TVSExample::reallyQuitQuestion()
{
  MessageDialog d("Are you sure you want to quit this program?", true, MESSAGE_QUESTION, BUTTONS_YES_NO, true);
  d.set_title("Really quit?");

  return (d.run() == Gtk::RESPONSE_YES);
}

bool TVSExample::onCloseButton(GdkEventAny * event)
{
  cout << "Close button pressed"<<endl;

  // If Yes button pressed (reallyQuitQuestion() returns true),
  // propagate the event. (return false)
  return (!reallyQuitQuestion());
}

void TVSExample::dataFill()
{
  // From: http://www.pleated-jeans.com/2012/02/20/slightly-skewed-movie-descriptions-11-pics/
  // But it can be found in lots of webs
  insertRow(1, "Inception", "Some people fall asleep in a van");
  insertRow(2, "Beauty and the Beast", "A monster locks a girl in his castle until she loves him");
  insertRow(3, "The Shining", "A man goes to a hotel and writes the worst novel ever");
  insertRow(4, "Frankenstein", "A lonely scientist makes a new friend");
  insertRow(5, "Up", "An old man moves to a more secluded neighborhood");
  insertRow(6, "Citizen Kane", "A newspaper tycoon wants to go sledding");
  insertRow(7, "Lord of the Rings", "A bunch of straight men fight over jewelry");

}

void TVSExample::insertRow(unsigned id, Glib :: ustring name, Glib :: ustring description)
{
  TreeModel::Row r = *(refTreeModel->append());

  r[columns.c_id] = id;
  r[columns.c_name] = name;
  r[columns.c_description] = description;
  r[columns.c_visible] = true;
}

bool TVSExample::onKeyRelease(GdkEventKey * event)
{
  if (w_textEntry.get_text().empty())
    {
      cout << "Empty text!" << endl;
      showAllRows();
    }

  for (TreeModel::Children::iterator it = refTreeModel->children().begin();
       it != refTreeModel->children().end();
       ++it)
    {
      TreeModel::Row r = *it;
     
      r[columns.c_visible] = (r.get_value(columns.c_name).find(w_textEntry.get_text()) != Glib::ustring::npos);
    }
}

void TVSExample::showAllRows()
{
  for (TreeModel::Children::iterator it = refTreeModel->children().begin();
       it != refTreeModel->children().end();
       ++it)
    {
      TreeModel::Row r = *it;
      r[columns.c_visible] = true;
    }
}

main.cpp – Programa principal

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "tvsexample.h"
#include <gtkmm.h>

using namespace Gtk;

int main(int argc, char *argv[])
{
  Glib::RefPtr<Application> app = Application::create(argc, argv);

  TVSExample window;

  return app->run(window);
}
  1. Lisardo
    11 marzo, 2014 9:36 | #1
    Usando Google Chrome Google Chrome 33.0.1750.146 en Linux Linux

    Hola Gaspar,
    He felicitarte por tener un blog especialmente bien cuidado, estructurado y fácil de seguir.
    Tengo un problema con gtkmm sobre el que me gustaría conocer tu opinión:
    Estoy desarrollando un interfaz que maneja un pequeño robot, recibe datos de sus sensores y actualiza datos procedentes de un joystick que también mueve el robotico.
    Mi problema es que se pierde la conexión con la interfaz. El programa sigue funcionando en el fondo, pero la GUi se queda bloqueada.
    La interfaz la he desarrollado con Glade y tengo el fichero xml conectado con un builder.
    Al trabajar con varios hilos que atienden diferentes puertos y sockets, utilizo mutex para acceder al refresco de los widgets de pantalla… ¿pero… podría tratarse de demasiados accesos a la interfaz o que tengo que bloquear algo más..?… en fin… un mar de dudas…
    Gracias por la atención
    Un cordial saludo

  2. Gaspar Fernández
    14 marzo, 2014 16:31 | #2
    Usando Mozilla Firefox Mozilla Firefox 27.0 en Ubuntu Linux Ubuntu Linux

    Muchas gracias Lisardo.

    Es difícil decirte algo sin echar un vistazo al código, pero si en el thread principal tienes el GUI y mientras haces las lecturas hay un bloqueo tal vez los mensajes no lleguen a gtk. Intenta utilizar alguna herramienta de depuración (gdb por ejemplo) o intenta crear un pequeño log (aunque sea en terminal) de lo que está sucediendo en el programa con el fin de determinar qué se efectúa para quedarse bloqueado.

    Si por otro caso es lo que dices (demasiados accesos a la interfaz), lo que se puede producir si intentas acceder muchísimas veces por segundo, puedes intentar, en lugar de mandar los datos constantemente a la interfaz, escribirlos en una memoria compartida y disparar cada segundo o así una signal_timeout() que lo actualice todo.

    Suerte !!

  1. No trackbacks yet.

Current ye@r *

Top