Form

A Gnome::Db::Form is in many ways similar to a Gnome::Db::Grid except that it does not show the data in a table but instead shows only one data set (usually one record) at a time using appropriate widgets. It features buttons to navigate through the data set and can also be used to edit data if modification queries were given, as stated in the Gnome::Db::Grid section.

Gnome::Db::Form reference

Example

This examples creates a read-write Form with a query from an example sqlite database.

Figure 5.2. Form

Form

Source Code

File: examplewindow.h

#include <gtkmm.h>
#include <libgdamm.h>
#include <libgnomedbmm.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow(const Glib::RefPtr<Gnome::Gda::Dict>& dict);
    
private:
#ifdef GLIBMM_EXCEPTIONS_ENABLED
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict);
#else
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict, std::auto_ptr<Glib::Error>& error);
#endif
  Glib::RefPtr<Gnome::Gda::DataModelQuery> m_model;
    
  Gtk::VBox m_box;
  Gtk::Label m_label;
  Gnome::Db::Form* m_form;
};

File: main.cc

#include <libgnomedbmm.h>
#include <libgdamm.h>
#include <gtkmm.h>

#include <iostream>

#include "examplewindow.h"

#ifdef GLIBMM_EXCEPTIONS_ENABLED
Glib::RefPtr<Gnome::Gda::Dict> create_dict()
#else
Glib::RefPtr<Gnome::Gda::Dict> create_dict(std::auto_ptr<Glib::Error>& error)
#endif
{
  Glib::RefPtr<Gnome::Gda::Dict> dict = Gnome::Gda::Dict::create();
  Glib::RefPtr<Gnome::Gda::Client> client = Gnome::Gda::Client::create();
  Glib::ustring connection_string = "DB_DIR=" LIBGNOMEDB_DATADIR ";DB_NAME=demo_db";

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  // Create a connection to the embedded sqlite database
  Glib::RefPtr<Gnome::Gda::Connection> cnc = client->open_connection_from_string("SQLite", connection_string, "" /* username */, "" /* password */);

  // We use a Gda::Dict here to read the information how to map DMBS types to Gda types from an xml file.
  // A Gda::Dict is a powerful repository to store informations about the underlying database and read/write them
  // to xml in an efficient way. Besides types it can also store other informations such as functions and aggregates.
  dict->set_connection(cnc);
  dict->update_dbms_meta_data();
#else
  Glib::RefPtr<Gnome::Gda::Connection> cnc = client->open_connection_from_string("SQLite", connection_string, "" /* username */, "" /* password */, Gnome::Gda::ConnectionOptions(0), error);
  if(error.get() == NULL)
  {
    dict->set_connection(cnc);
    dict->update_dbms_meta_data(error);
  }
#endif

  return dict;
}

int main(int argc, char* argv[])
{
  Gtk::Main kit(argc, argv);
  Gnome::Db::init("Form example", "1.0", argc, argv);

  Glib::RefPtr<Gnome::Gda::Dict> dict;

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
    dict = create_dict();
  }
  catch(const Glib::Error& err)
  {
    std::cerr << err.what() << std::endl;
    exit(1);
  }
#else
  std::auto_ptr<Glib::Error> error;
  dict = create_dict(error);
  if(error.get() != NULL)
  {
    std::cerr << error->what() << std::endl;
    exit(1);
  }
#endif

  ExampleWindow window(dict);

  kit.run(window);
  return 0;
}

File: examplewindow.cc

#include "examplewindow.h" 

#include <libgdamm.h>
#include <iostream>

#ifdef GLIBMM_EXCEPTIONS_ENABLED
void ExampleWindow::create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict)
#else
void ExampleWindow::create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict, std::auto_ptr<Glib::Error>& error)
#endif // GLIBMM_EXCEPTIONS_ENABLED
{
  Glib::RefPtr<Gnome::Gda::Query> query = Gnome::Gda::Query::create(dict);

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  query->set_sql_text("SELECT ref, category, name, price, wh_stored FROM products");
#else
  query->set_sql_text("SELECT ref, category, name, price, wh_stored FROM products", error);
  if(error.get() != NULL) return;
#endif // GLIBMM_EXCEPTIONS_ENABLED
  
  m_model = Gnome::Gda::DataModelQuery::create(query);

  const Glib::ustring update_query =
                 "UPDATE products set "
                 "ref=##/*name:'+0' type:gchararray*/, "
                 "category=##/*name:'+1' type:gint*/,"
                 "name=##/*name:'+2' type:gchararray*/, "
                 "wh_stored=##/*name:'+4' type:gint*/ "
                 "WHERE ref=##/*name:'-0' type:gchararray*/";

  const Glib::ustring delete_query =
                 "DELETE FROM products WHERE ref=##/*name:'-0' type:gchararray*/";

  const Glib::ustring insert_query =
                 "INSERT INTO products (ref, category, name, price, wh_stored) "
                 "VALUES (##/*name:'+0' type:gchararray*/, "
                 "##/*name:'+1' type:gint*/, "
                 "##/*name:'+2' type:gchararray*/, "
                 "1.0, "
                 "##/*name:'+4' type:gint*/)";

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  m_model->set_modification_query(update_query);
  m_model->set_modification_query(delete_query);
  m_model->set_modification_query(insert_query);
#else
  m_model->set_modification_query(update_query, error);
  m_model->set_modification_query(delete_query, error);
  m_model->set_modification_query(insert_query, error);
#endif
}

ExampleWindow::ExampleWindow(const Glib::RefPtr<Gnome::Gda::Dict>& dict) :
  m_label("The following Gnome::Db::Form widget displays data from the 'products' table.\n\n"
          "Because modification queries were provided, the data is writeable\n(except for the 'price' "
          "field, because these queries voluntarily omit that field)."),
  m_form(0)
{    
  m_box.set_border_width(6);
  m_box.pack_start(m_label, Gtk::PACK_SHRINK);

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
    create_model(dict);
  }
  catch(const Glib::Error& err)
  {
    std::cerr << "Exception caught: " << err.what() << std::endl;
    exit(1);
  }
#else
  std::auto_ptr<Glib::Error> error;
  create_model(dict, error);
  if(error.get() != NULL)
  {
    std::cerr << "Exception caught: " << error->what() << std::endl;
    exit(1);
  }
#endif

  /* Create the demo widget */
  m_form = Gtk::manage(new Gnome::Db::Form(m_model));
  m_box.pack_start(*m_form);
  add(m_box);
  show_all();
}

Setting fields as mandatory

Often you might have fields in your database that should never be empty. Gnome::Db::Form can support you by adding a '*' next to every field that is mandatory and showing an error message if the user leaves this field empty. Empty in this case means an empty string or an empty Gnome::Gda::Value.

To set a field as mandatory you must access the underlying Gda::Parameter of your Gda::DataModel and set the parameter to not-null:

// Configure param to be mandatory:
// Note that this must be done before the form is created.
Glib::RefPtr<Gnome::Gda::DataModelIter> iter = m_model->create_iter();
Glib::RefPtr<Gnome::Gda::Parameter> param = iter->find_param("name");
param->set_not_null();

Example

Figure 5.3. Mandatory fields in form

Mandatory fields in form

Source Code

File: examplewindow.h

#include <gtkmm.h>
#include <libgdamm.h>
#include <libgdamm/datamodelquery.h>
#include <libgnomedbmm.h>

class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow(Glib::RefPtr<Gnome::Gda::Dict>& dict);

private:
#ifdef GLIBMM_EXCEPTIONS_ENABLED
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict);
#else
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict, std::auto_ptr<Glib::Error>& error);
#endif // GLIBMM_EXCEPTIONS_ENABLED

  Glib::RefPtr<Gnome::Gda::DataModelQuery> m_model;
  
  Gtk::VBox m_box;
  Gtk::Label m_label;
  Gnome::Db::Form* m_form;
};

 

File: main.cc

#include <libgnomedbmm.h>
#include <libgdamm.h>
#include <gtkmm.h>

#include <iostream>

#include "examplewindow.h"

#ifdef GLIBMM_EXCEPTIONS_ENABLED
Glib::RefPtr<Gnome::Gda::Dict> create_dict()
#else
Glib::RefPtr<Gnome::Gda::Dict> create_dict(std::auto_ptr<Glib::Error>& error)
#endif
{
  Glib::RefPtr<Gnome::Gda::Dict> dict = Gnome::Gda::Dict::create();
  Glib::RefPtr<Gnome::Gda::Client> client = Gnome::Gda::Client::create();
  Glib::ustring connection_string = "DB_DIR=" LIBGNOMEDB_DATADIR ";DB_NAME=demo_db";

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  // Create a connection to the embedded sqlite database
  Glib::RefPtr<Gnome::Gda::Connection> cnc = client->open_connection_from_string("SQLite", connection_string, "" /* username */, "" /* password */);

  // We use a Gda::Dict here to read the information how to map DMBS types to Gda types from an xml file.
  // A Gda::Dict is a powerful repository to store informations about the underlying database and read/write them
  // to xml in an efficient way. Besides types it can also store other informations such as functions and aggregates.
  dict->set_connection(cnc);
  dict->update_dbms_meta_data();
#else
  Glib::RefPtr<Gnome::Gda::Connection> cnc = client->open_connection_from_string("SQLite", connection_string, "", "", Gnome::Gda::ConnectionOptions(0), error);
  if(error.get() == NULL)
  {
    dict->set_connection(cnc);
    dict->update_dbms_meta_data(error);
  }
#endif

  return dict;
}

int main(int argc, char* argv[])
{
  Gtk::Main kit(argc, argv);
  Gnome::Db::init("Mandatory example", "1.0", argc, argv);

  Glib::RefPtr<Gnome::Gda::Dict> dict;

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
    dict = create_dict();
  }
  catch(const Glib::Error& err)
  {
    std::cerr << err.what() << std::endl;
    exit(1);
  }
#else
  std::auto_ptr<Glib::Error> error;
  dict = create_dict(error);
  if(error.get() != NULL)
  {
    std::cerr << error->what() << std::endl;
    exit(1);
  }
#endif

  ExampleWindow window(dict);

  kit.run(window);
  return 0;
}

File: examplewindow.cc

#include "examplewindow.h" 

#include <libgdamm/query.h>
#include <libgdamm/datamodelquery.h>
#include <libgdamm/datamodel.h>
#include <iostream>

#ifdef GLIBMM_EXCEPTIONS_ENABLED
void ExampleWindow::create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict)
#else
void ExampleWindow::create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict, std::auto_ptr<Glib::Error>& error)
#endif // GLIBMM_EXCEPTIONS_ENABLED
{
  Glib::RefPtr<Gnome::Gda::Query> query = Gnome::Gda::Query::create(dict);

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  query->set_sql_text("SELECT id, name, country, city FROM warehouses");
#else
  query->set_sql_text("SELECT id, name, country, city FROM warehouses", error);
  if(error.get() != NULL) return;
#endif // GLIBMM_EXCEPTIONS_ENABLED
	
  // Create the demo widget:	
  m_model = Gnome::Gda::DataModelQuery::create(query);

  // TODO: Explain what these SQL queries do:
  const Glib::ustring update_query = "UPDATE warehouses set "
                                     "id=##/*name:'+0' type:gint*/, "
                                     "name=##/*name:'+1' type:gchararray*/,"
                                     "country=##/*name:'+2' type:gchararray*/, "
                                     "city=##/*name:'+3' type:gchararray*/ "
                                     "WHERE id=##/*name:'-0' type:gint*/";

  const Glib::ustring delete_query = "DELETE FROM warehouses WHERE id=##/*name:'-0' type:gint*/";

  const Glib::ustring insert_query = "INSERT INTO warehouses (id, name, country, city) "
                                     "VALUES (##/*name:'+0' type:gint*/, "
                                     "##/*name:'+1' type:gchararray*/, "
                                     "##/*name:'+2' type:gchararray*/, "
                                     "##/*name:'+3' type:gchararray*/)";

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  m_model->set_modification_query(update_query);
  m_model->set_modification_query(delete_query);
  m_model->set_modification_query(insert_query);
#else
  m_model->set_modification_query(update_query, error);
  m_model->set_modification_query(delete_query, error);
  m_model->set_modification_query(insert_query, error);
#endif // GLIBMM_EXCEPTIONS_ENABLED
}

ExampleWindow::ExampleWindow(Glib::RefPtr<Gnome::Gda::Dict>& dict) :
  m_label("The following Gnome::Db::Form show how fields can become mandatory. Try to add a\n"
          "new dataset using the '+' button. You should get an error message if you omit a field that\n"
          "has been marked mandatory with a '*'"),
  m_form(0)
{		
  m_box.set_border_width(6);
  m_box.pack_start(m_label, Gtk::PACK_SHRINK);

#ifdef GLIBMM_EXCEPTIONS_ENABLED
  try
  {
    create_model(dict);
  }
  catch(const Glib::Error& err)
  {
    std::cerr << "Exception Caught: " << err.what() << std::endl;
    exit(1);
  }
#else
  std::auto_ptr<Glib::Error> error;
  create_model(dict, error);
  if(error.get() != NULL)
  {
    std::cerr << "Exception Caught: " << error->what() << std::endl;
    exit(1);
  }
#endif // GLIBMM_EXCEPTIONS_ENABLED

  // Configure param to be mandatory
  // Note that this has to be done before the form is created
  m_form = Gtk::manage(new Gnome::Db::Form(m_model));
  Glib::RefPtr<Gnome::Gda::DataModelIter> iter = m_form->get_raw_form()->get_current_data();
  Glib::RefPtr<Gnome::Gda::Parameter> id = iter->find_param("id");
  Glib::RefPtr<Gnome::Gda::Parameter> name = iter->find_param("name");
  Glib::RefPtr<Gnome::Gda::Parameter> country = iter->find_param("country");  
  Glib::RefPtr<Gnome::Gda::Parameter> city = iter->find_param("city");
  if (name && city && country && id)
  {
    // This seems to have absolutely no effect!
    id->set_not_null(false);
    name->set_not_null(true);
    country->set_not_null(false);
    city->set_not_null(false);
  }
  else
  {
    std::cerr << "Param not found" << std::endl;
    exit(1);
  }

  m_box.pack_start(*m_form);
  add(m_box);
  show_all();
}

Changing write mode

In the previous examples, any modified data was not written back to the database until the user pressed the commit button on the bottom of the form. However, there are more so-called write-modes. To change it, you first have to obtain the underlaying Gnome::Db::RawForm (via Gnome::Db::Form::get_raw_form) and then call set_write_mode() on it. There are four of these write modes:

Table 5.1. Defined write modes

Gnome::Db::DATA_WIDGET_WRITE_ON_DEMAND Modifications are written back on explicit demand (in this case all the modifications made to any row are written back). This is the default.
Gnome::Db::DATA_WIDGET_WRITE_ON_ROW_CHANGE Modifications are written back when the current row changes (in this case only the modifications on the current row are written back)
Gnome::Db::DATA_WIDGET_WRITE_ON_VALUE_ACTIVATED Modifications are written back when the user "activates" the data entry (for example by pressing ENTER in a single line data entry), in this case only the modifications on the current row are written back
Gnome::Db::DATA_WIDGET_WRITE_ON_VALUE_CHANGE Modifications are written back everytime a value changes; in this case only the modifications on the current row are written back (this option should only be used if the "source" data model accepts modifications without any test and without processing delays).

The complete call to change the write mode to, say, WRITE_ON_VALUE_ACTIVATED, looks like this (suppose form is a Gnome::Db::Form):

form.get_raw_form()->set_write_mode(Gnome::Db::DATA_WIDGET_WRITE_ON_VALUE_ACTIVATED);

Note that the set_write_mode() function is provided by the Gnome::Db::DataWidget base class from which also Gnome::Db::RawGrid derives. This means that it is possible to change the write mode of a grid in the same way. The only difference is that the WRITE_ON_VALUE_CHANGE mode is equivalent to the WRITE_ON_VALUE_ACTIVATED mode for the grid.