[glib/wip/gproperty-2: 55/57] docs: Update the GObject tutorial section on properties
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/gproperty-2: 55/57] docs: Update the GObject tutorial section on properties
- Date: Sun, 7 Jul 2013 15:17:59 +0000 (UTC)
commit b4e3aa15c7445c9d26ed7dd53e978d4a4edf39b3
Author: Emmanuele Bassi <ebassi gnome org>
Date: Sun Jun 23 18:55:05 2013 +0100
docs: Update the GObject tutorial section on properties
Get rid of a bunch of Maman stuff, and use the new macros and functions
instead of the existing GParamSpec API.
https://bugzilla.gnome.org/show_bug.cgi?id=648526
docs/reference/gobject/tut_gobject.xml | 347 +++++++++++++-------------------
1 files changed, 136 insertions(+), 211 deletions(-)
---
diff --git a/docs/reference/gobject/tut_gobject.xml b/docs/reference/gobject/tut_gobject.xml
index c3b8ed1..006793c 100644
--- a/docs/reference/gobject/tut_gobject.xml
+++ b/docs/reference/gobject/tut_gobject.xml
@@ -495,254 +495,179 @@ void g_object_run_dispose (GObject *object);
One of GObject's nice features is its generic get/set mechanism for object
properties. When an object
is instantiated, the object's class_init handler should be used to register
- the object's properties with <function><link
linkend="g-object-class-install-properties">g_object_class_install_properties</link></function>
- (implemented in <filename>gobject.c</filename>).
+ the object's properties with the <function>G_DEFINE_PROPERTIES</function>
+ macro.
</para>
<para>
The best way to understand how object properties work is by looking at a real example
on how it is used:
<informalexample><programlisting>
-/************************************************/
-/* Implementation */
-/************************************************/
+typedef struct {
+ char *name;
+ gint age;
+} PersonPrivate;
-enum
-{
- PROP_0,
-
- PROP_MAMAN_NAME,
- PROP_PAPA_NUMBER,
-
- N_PROPERTIES
-};
-
-static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
+G_DEFINE_TYPE_WITH_PRIVATE (Person, person, G_TYPE_OBJECT)
static void
-maman_bar_set_property (GObject *object,
- guint property_id,
- const GValue *value,
- GParamSpec *pspec)
+person_finalize (GObject *gobject)
{
- MamanBar *self = MAMAN_BAR (object);
-
- switch (property_id)
- {
- case PROP_MAMAN_NAME:
- g_free (self->priv->name);
- self->priv->name = g_value_dup_string (value);
- g_print ("maman: %s\n", self->priv->name);
- break;
-
- case PROP_PAPA_NUMBER:
- self->priv->papa_number = g_value_get_uchar (value);
- g_print ("papa: %u\n", self->priv->papa_number);
- break;
-
- default:
- /* We don't have any other property... */
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
-}
+ PersonPrivate *priv = person_get_instance_private ((Person *) gobject);
-static void
-maman_bar_get_property (GObject *object,
- guint property_id,
- GValue *value,
- GParamSpec *pspec)
-{
- MamanBar *self = MAMAN_BAR (object);
-
- switch (property_id)
- {
- case PROP_MAMAN_NAME:
- g_value_set_string (value, self->priv->name);
- break;
-
- case PROP_PAPA_NUMBER:
- g_value_set_uchar (value, self->priv->papa_number);
- break;
-
- default:
- /* We don't have any other property... */
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
- break;
- }
+ g_free (priv->name);
+
+ G_OBJECT_CLASS (person_parent_class)->finalize (gobject);
}
static void
-maman_bar_class_init (MamanBarClass *klass)
+person_class_init (PersonClass *klass)
{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ G_OBJECT_CLASS (klass)->finalize = person_finalize;
- gobject_class->set_property = maman_bar_set_property;
- gobject_class->get_property = maman_bar_get_property;
-
- obj_properties[PROP_NAME] =
- g_param_spec_string ("maman-name",
- "Maman construct prop",
- "Set maman's name",
- "no-name-set" /* default value */,
- G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
-
- obj_properties[PROP_NUMBER] =
- g_param_spec_uchar ("papa-number",
- "Number of current Papa",
- "Set/Get papa's number",
- 0 /* minimum value */,
- 10 /* maximum value */,
- 2 /* default value */,
- G_PARAM_READWRITE);
-
- g_object_class_install_properties (gobject_class,
- N_PROPERTIES,
- obj_properties);
+ G_DEFINE_PROPERTIES (Person, person, klass,
+ G_DEFINE_PROPERTY (Person, string, name, G_PROPERTY_READWRITE | G_PROPERTY_COPY_SET)
+ G_DEFINE_PROPERTY (Person, uint, age, G_PROPERTY_READWRITE))
}
+</programlisting></informalexample>
+ The code above will add two properties, <emphasis>name</emphasis> and
+ <emphasis>age</emphasis> to the <emphasis>Person</emphasis> class;
+ the name property is a string, and the age property is a generic unsigned
+ integer. Both properties are readable and writable. Additionally, the name
+ property is implemented by copying the string passed to the setter, but
+ the get function will return a pointer. By default, the properties will
+ be stored inside the private data structure on a Person instance, using
+ the given field name.
+ </para>
-/************************************************/
-/* Use */
-/************************************************/
+ <para>
+ Once we define the properties, their types, their access semantics,
+ and their visibility, we can access them through the generic GObject
+ API:
+<informalexample><programlisting>
+Person *person;
+
+person = g_object_new (person_get_type (), NULL);
+
+g_object_set (person,
+ "name", "Rupert S. Monkey",
+ "age", 33,
+ NULL);
+</programlisting></informalexample>
+ The code above will set the two properties on the <emphasis>person</emphasis>
+ instance.
+ </para>
-GObject *bar;
-GValue val = G_VALUE_INIT;
+ <para>
+ It should be noted that using the generic GObject API should not be the
+ common pattern for setting properties in C. The generic API uses variadic
+ arguments, which have side effects in terms of error handling, type
+ inference, and argument termination; those side effects can lead to
+ hard to track bugs. One option to avoid the variadic arguments API is to
+ use the GValue-based one, e.g.
+<informalexample><programlisting>
+GValue name_value = G_VALUE_INIT;
+GValue age_value = G_VALUE_INIT;
-bar = g_object_new (MAMAN_TYPE_SUBBAR, NULL);
+g_value_init (&name_value, G_TYPE_STRING);
+g_value_set_string (&name_value, "Rupert S. Monkey");
-g_value_init (&val, G_TYPE_CHAR);
-g_value_set_char (&val, 11);
+g_value_init (&age_value, G_TYPE_INT);
+g_value_set_int (&age_value, 33);
-g_object_set_property (G_OBJECT (bar), "papa-number", &val);
+g_object_set_property (G_OBJECT (person), "name", &name_value);
+g_object_set_property (G_OBJECT (person), "age", &age_value);
-g_value_unset (&val);
+g_value_unset (&name_value);
+g_value_unset (&age_value);
</programlisting></informalexample>
- The client code just above looks simple but a lot of things happen under the hood:
- </para>
-
- <para>
- <function><link linkend="g-object-set-property">g_object_set_property</link></function> first ensures
a property
- with this name was registered in bar's class_init handler. If so it walks the class hierarchy,
- from bottom, most derived type, to top, fundamental type to find the class
- which registered that property. It then tries to convert the user-provided GValue
- into a GValue whose type is that of the associated property.
+ But, as you can see from the example above, the amount of code necessary
+ balloons rapidly out of control.
</para>
-
- <para>
- If the user provides a signed char GValue, as is shown
- here, and if the object's property was registered as an unsigned int,
- <function><link linkend="g-value-transform">g_value_transform</link></function> will try to transform
the input signed char into
- an unsigned int. Of course, the success of the transformation depends on the availability
- of the required transform function. In practice, there will almost always be a transformation
- <footnote>
- <para>Its behaviour might not be what you expect but it is up to you to actually avoid
- relying on these transformations.
- </para>
- </footnote>
- which matches and conversion will be carried out if needed.
- </para>
-
- <para>
- After transformation, the <link linkend="GValue"><type>GValue</type></link> is validated by
- <function><link linkend="g-param-value-validate">g_param_value_validate</link></function> which makes
sure the user's
- data stored in the <link linkend="GValue"><type>GValue</type></link> matches the characteristics
specified by
- the property's <link linkend="GParamSpec"><type>GParamSpec</type></link>.
- Here, the <link linkend="GParamSpec"><type>GParamSpec</type></link> we
- provided in class_init has a validation function which makes sure that the GValue
- contains a value which respects the minimum and maximum bounds of the
- <link linkend="GParamSpec"><type>GParamSpec</type></link>. In the example above, the client's GValue
does not
- respect these constraints (it is set to 11, while the maximum is 10). As such, the
- <function><link linkend="g-object-set-property">g_object_set_property</link></function> function will
return with an error.
- </para>
-
- <para>
- If the user's GValue had been set to a valid value, <function><link
linkend="g-object-set-property">g_object_set_property</link></function>
- would have proceeded with calling the object's set_property class method. Here, since our
- implementation of Foo did override this method, the code path would jump to
- <function>foo_set_property</function> after having retrieved from the
- <link linkend="GParamSpec"><type>GParamSpec</type></link> the <emphasis>param_id</emphasis>
- <footnote>
- <para>
- It should be noted that the param_id used here need only to uniquely identify each
- <link linkend="GParamSpec"><type>GParamSpec</type></link> within the <type>FooClass</type> such
that the switch
- used in the set and get methods actually works. Of course, this locally-unique
- integer is purely an optimization: it would have been possible to use a set of
- <emphasis>if (strcmp (a, b) == 0) {} else if (strcmp (a, b) == 0) {}</emphasis> statements.
- </para>
- </footnote>
- which had been stored by
- <function><link
linkend="g-object-class-install-property">g_object_class_install_property</link></function>.
- </para>
-
- <para>
- Once the property has been set by the object's set_property class method, the code path
- returns to <function><link linkend="g-object-set-property">g_object_set_property</link></function>
which makes sure that
- the "notify" signal is emitted on the object's instance with the changed property as
- parameter unless notifications were frozen by <function><link
linkend="g-object-freeze-notify">g_object_freeze_notify</link></function>.
- </para>
-
+
<para>
- <function><link linkend="g-object-thaw-notify">g_object_thaw_notify</link></function> can be used to
re-enable notification of
- property modifications through the "notify" signal. It is important to remember that
- even if properties are changed while property change notification is frozen, the "notify"
- signal will be emitted once for each of these changed properties as soon as the property
- change notification is thawed: no property change is lost for the "notify" signal. Signal
- can only be delayed by the notification freezing mechanism.
+ The preferred way to access properties on a GObject is to use accessor
+ functions, which guarantee type safety when compiling code, and avoid
+ large amounts of code. For instance:
+<informalexample><programlisting>
+void
+person_set_name (Person *person, const char *name)
+{
+ GProperty *property;
+
+ property = g_object_class_find_property (G_OBJECT_GET_CLASS (person), "name");
+ g_property_set (property, person, name);
+}
+
+const char *
+person_get_name (Person *person)
+{
+ PersonPrivate *priv = person_get_instance_private (person);
+
+ return priv->name;
+}
+</programlisting></informalexample>
</para>
-
+
<para>
- It sounds like a tedious task to set up GValues every time when one wants to modify a property.
- In practice one will rarely do this. The functions <function><link
linkend="g-object-set-property">g_object_set_property</link></function>
- and <function><link linkend="g-object-get-property">g_object_get_property</link></function>
- are meant to be used by language bindings. For application there is an easier way and
- that is described next.
+ GObject provides pre-processor macros to simplify the developer's job at
+ creating these functions, which result in cleaner and simpler code; for
+ instance, the example above can be effectively replaced by a single
+ macro:
+<informalexample><programlisting>
+G_DEFINE_PROPERTY_GET_SET (Person, person, const char *, name)
+</programlisting></informalexample>
+ The macro above will generate both the get function and the set one.
</para>
- <sect2 id="gobject-multi-properties">
- <title>Accessing multiple properties at once</title>
-
- <para>
- It is interesting to note that the <function><link
linkend="g-object-set">g_object_set</link></function> and
- <function><link linkend="g-object-set-valist">g_object_set_valist</link></function> (vararg version)
functions can be used to set
- multiple properties at once. The client code shown above can then be re-written as:
-<programlisting>
-MamanBar *foo;
-foo = /* */;
-g_object_set (G_OBJECT (foo),
- "papa-number", 2,
- "maman-name", "test",
- NULL);
-</programlisting>
- This saves us from managing the GValues that we were needing to handle when using
- <function><link linkend="g-object-set-property">g_object_set_property</link></function>.
- The code above will trigger one notify signal emission for each property modified.
- </para>
-
+ <sect2>
+ <title>Property notification and binding</title>
+
<para>
- Of course, the _get versions are also available: <function><link
linkend="g-object-get">g_object_get</link></function>
- and <function><link linkend="g-object-get-valist">g_object_get_valist</link></function> (vararg
version) can be used to get numerous
- properties at once.
+ Every time a property is set to a new value, GObject will emit a signal
+ called <emphasis>notify</emphasis>; this signal can be used to update
+ ancillary code every time the state of an instance changes; for instance,
+ it is possible to update the text field of a GUI with the content of the
+ person's name, if the Person instance is changed by another part of the
+ GUI, or by another source.
</para>
-
+
<para>
- These high level functions have one drawback - they don't provide a return result.
- One should pay attention to the argument types and ranges when using them.
- A known source of errors is to e.g. pass a gfloat instead of a gdouble and thus
- shifting all subsequent parameters by four bytes. Also forgetting the terminating
- NULL will lead to unexpected behaviour.
+ The notification signal is detailed with the name of the property which
+ has changed, so it's possible to subscribe to changes to specific
+ properties, for instance:
+<informalexample><programlisting>
+static void
+on_name_change (GObject *gobject,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ Person *person = (Person *) gobject;
+ GtkLabel *label = user_data;
+
+ gtk_label_set_text (label, person_get_name (person));
+}
+
+ ...
+ g_signal_connect (person, "notify::name", G_CALLBACK (on_name_change), ui_label);
+ ...
+</programlisting></informalexample>
+ The code above will update the contents of a GtkLabel using the name
+ property every time the property changes.
</para>
-
+
<para>
- Really attentive readers now understand how <function><link
linkend="g-object-new">g_object_new</link></function>,
- <function><link linkend="g-object-newv">g_object_newv</link></function> and <function><link
linkend="g-object-new-valist">g_object_new_valist</link></function>
- work: they parse the user-provided variable number of parameters and invoke
- <function><link linkend="g-object-set">g_object_set</link></function> on the parameters only after
the object has been successfully constructed.
- Of course, the "notify" signal will be emitted for each property set.
+ It is also possible to use the property bindings to tie together a
+ property on a source instance and a property on a target instance.
+ In the example above, we could bind together the name property on a
+ Person instance to the text property on a GtkLabel instance:
+<informalexample><programlisting>
+g_object_bind_property (person, "name", label, "text", G_BINDING_DEFAULT);
+</programlisting></informalexample>
+ Thus removing the need to have an explicit function doing so.
</para>
-
- </sect2>
-<!-- @todo tell here about how to pass use handle properties in derived classes -->
+ </sect2>
</sect1>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]