跳转至内容

GTK+ 示例/树形视图/自定义单元格渲染器

来自维基教科书,开放书籍,开放世界

编写自定义单元格渲染器

[编辑 | 编辑源代码]

与 Gtk+ 一起提供的单元格渲染器应该足以满足大多数目的,但可能在某些情况下,您希望在树形视图中显示无法使用提供的单元格渲染器显示的内容,或者您希望从提供的单元格渲染器中派生以扩展其功能。

您可以通过编写一个从 GtkCellRenderer 派生的新对象来实现这一点(如果您只想扩展现有对象,甚至可以从其他单元格渲染器中派生)。

在该过程中,您需要做三件事

  • 使用类型系统注册您的渲染器所需的一些新属性,并编写您自己的 set_property 和 get_property 函数来设置和获取您的新渲染器的属性。
  • 编写您自己的 cell_renderer_get_size 函数并覆盖父对象的函数(通常父对象是 GtkCellRenderer 类型。请注意,您应该在这里尊重父对象的填充和单元格对齐的标准属性)。
  • 编写您自己的 cell_renderer_render 函数并覆盖父对象的函数。此函数执行实际渲染。

编写新单元格渲染器的 GObject 类型系统内容类似于我们在编写自定义树模型时所做的内容,在这种情况下相对简单。复制粘贴并根据您的需要修改。

在 Gtk+ 源代码树中,可以查看或修改单元格渲染器代码的良好示例是 GtkCellRendererPixbuf 和 GtkCellRendererToggle。这两个案例都不到 500 行代码,因此应该很容易理解。12.1. 工作示例:进度条单元格渲染器

在以下示例中,我们将编写一个自定义单元格渲染器,以便将进度条渲染到树形视图中(代码“很大程度上受到” Sean Egan 在 GAIM 中的进度条单元格渲染器实现的启发;请注意,这仅仅是为了演示目的,Gtk+ 已经有一段时间了。现在有一个进度条单元格渲染器:有关详细信息,请参阅 GtkCellRendererProgress API 文档)。

  • custom-cell-renderer-progressbar.h
  • custom-cell-renderer-progressbar.c
  • main.c

custom-cell-renderer-progressbar.h

[编辑 | 编辑源代码]

头文件包含通常的 GObject 类型转换和类型检查定义以及我们的 CustomCellRendererProgress 结构。如父类型所示,我们从 GtkCellRenderer 派生。父对象必须始终是结构中的第一个项目(还要注意,它不是指向对象的指针,而是嵌入在我们的结构中的父对象结构本身)。

我们的 CustomCellRendererProgress 结构相当平淡,只包含一个双精度浮点变量,我们在其中存储我们的新“百分比”属性(它将决定进度条的长度)。

#ifndef _custom_cell_renderer_progressbar_included_
#define _custom_cell_renderer_progressbar_included_

#include <gtk/gtk.h>

/* Some boilerplate GObject type check and type cast macros. */

#define CUSTOM_TYPE_CELL_RENDERER_PROGRESS             (custom_cell_renderer_progress_get_type())
G_DECLARE_DERIVABLE_TYPE(CustomCellRendererProgress, custom_cell_renderer_progress, CUSTOM, CELL_RENDERER_PROGRESS, GtkCellRenderer)


struct _CustomCellRendererProgressClass
{
  GtkCellRendererClass  parent_class;
};


GType                custom_cell_renderer_progress_get_type (void);

GtkCellRenderer     *custom_cell_renderer_progress_new (void);


#endif /* _custom_cell_renderer_progressbar_included_ */



custom-cell-renderer-progressbar.c

[编辑 | 编辑源代码]

代码包含上面描述的所有内容,所以让我们直接进入代码

#include "custom-cell-renderer-progressbar.h"

/* This is based mainly on GtkCellRendererProgress
 *  in GAIM, written and (c) 2002 by Sean Egan
 *  (Licensed under the GPL), which in turn is
 *  based on Gtk's GtkCellRenderer[Text|Toggle|Pixbuf]
 *  implementation by Jonathan Blandford */


typedef struct _CustomCellRendererProgressPrivate CustomCellRendererProgressPrivate;
struct _CustomCellRendererProgressPrivate
{
    gdouble progress;
};
G_DEFINE_TYPE_WITH_PRIVATE(CustomCellRendererProgress, custom_cell_renderer_progress,  GTK_TYPE_CELL_RENDERER)


/* Some boring function declarations: GObject type system stuff */

static void custom_cell_renderer_progress_init       (CustomCellRendererProgress      *cellprogress);

static void custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass);

static void custom_cell_renderer_progress_get_property  (GObject                    *object,
                                                             guint                       param_id,
                                                             GValue                     *value,
                                                             GParamSpec                 *pspec);

static void custom_cell_renderer_progress_set_property  (GObject                    *object,
                                                             guint                       param_id,
                                                             const GValue               *value,
                                                             GParamSpec                 *pspec);

static void custom_cell_renderer_progress_finalize (GObject *gobject);


/* These functions are the heart of our custom cell renderer: */

static void custom_cell_renderer_progress_get_preferred_width (
              GtkCellRenderer       *cell_renderer,
              GtkWidget             *parent_widget,
              int                   *minimal_size,
              int                   *natural_size);

static void custom_cell_renderer_progress_get_preferred_height (
              GtkCellRenderer       *cell_renderer,
              GtkWidget             *parent_widget,
              int                   *minimal_size,
              int                   *natural_size);

static void custom_cell_renderer_progress_render (
              GtkCellRenderer      *cell_renderer,
              cairo_t              *cr,
              GtkWidget            *widget,
              const GdkRectangle   *background_area,
              const GdkRectangle   *cell_area,
              GtkCellRendererState  flags);

enum
{
  PROP_PERCENTAGE = 1,
};

static   gpointer parent_class;

/******************************************************************
 *
 * CustomCellRendererProgressPrivate: shortcut function to access
 *                                    private data.
 *
 ******************************************************************/
static inline CustomCellRendererProgressPrivate *
private (CustomCellRendererProgress *self)
{
   return custom_cell_renderer_progress_get_instance_private(self);
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_init: set some default properties of the
 *                                      parent (GtkCellRenderer).
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_init (CustomCellRendererProgress *self)
{
  /* init parent */
  {
    GtkCellRenderer *parent = GTK_CELL_RENDERER(self);

    g_object_set (parent, "mode", GTK_CELL_RENDERER_MODE_INERT, NULL);
    g_object_set (parent, "xpad", 2, NULL);
    g_object_set (parent, "ypad", 2, NULL);
  }

  /* init private variables */
  {
   private (self)->progress = 0.0;
  }
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_class_init:
 *
 *  set up our own get_property and set_property functions, and
 *  override the parent's functions that we need to implement.
 *  And make our new "percentage" property known to the type system.
 *  If you want cells that can be activated on their own (ie. not
 *  just the whole row selected) or cells that are editable, you
 *  will need to override 'activate' and 'start_editing' as well.
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_class_init (CustomCellRendererProgressClass *klass)
{
  GtkCellRendererClass *cell_class   = GTK_CELL_RENDERER_CLASS(klass);
  GObjectClass         *object_class = G_OBJECT_CLASS(klass);

  parent_class           = g_type_class_peek_parent (klass);
  object_class->finalize = custom_cell_renderer_progress_finalize;

  /* Hook up functions to set and get our
   *   custom cell renderer properties */
  object_class->get_property = custom_cell_renderer_progress_get_property;
  object_class->set_property = custom_cell_renderer_progress_set_property;

  /* Override the crucial functions that are the heart
   *   of a cell renderer in the parent class */
  cell_class->get_preferred_width = custom_cell_renderer_progress_get_preferred_width;
  cell_class->get_preferred_height = custom_cell_renderer_progress_get_preferred_height;
  cell_class->render   = custom_cell_renderer_progress_render;

  /* Install our very own properties */
  g_object_class_install_property (object_class,
                                   PROP_PERCENTAGE,
                                   g_param_spec_double ("percentage",
                                                        "Percentage",
                                                         "The fractional progress to display",
                                                         0, 1, 0,
                                                         G_PARAM_READWRITE));
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_finalize: free any resources here
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_finalize (GObject *object)
{
/*
  CustomCellRendererProgress *cellrendererprogress = CUSTOM_CELL_RENDERER_PROGRESS(object);
*/

  /* Free any dynamically allocated resources here */

  (* G_OBJECT_CLASS (parent_class)->finalize) (object);
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_property: as it says
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_get_property (GObject    *object,
                                            guint       param_id,
                                            GValue     *value,
                                            GParamSpec *psec)
{
  CustomCellRendererProgress  *self = CUSTOM_CELL_RENDERER_PROGRESS(object);

  switch (param_id)
  {
    case PROP_PERCENTAGE:
      g_value_set_double(value, private (self)->progress);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec);
      break;
  }
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_set_property: as it says
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_set_property (GObject      *object,
                                            guint         param_id,
                                            const GValue *value,
                                            GParamSpec   *pspec)
{
  CustomCellRendererProgress *self = CUSTOM_CELL_RENDERER_PROGRESS (object);

  switch (param_id)
  {
    case PROP_PERCENTAGE:
      private (self)->progress = g_value_get_double(value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
      break;
  }
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_new: return a new cell renderer instance
 *
 ***************************************************************************/

GtkCellRenderer *
custom_cell_renderer_progress_new (void)
{
  return g_object_new(CUSTOM_TYPE_CELL_RENDERER_PROGRESS, NULL);
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_preferred_width:
 *            crucial - calculate the size of our cell, taking into account
 *             padding and alignment properties of parent.
 *
 ***************************************************************************/

#define FIXED_WIDTH   100
#define FIXED_HEIGHT  10

static void
custom_cell_renderer_progress_get_preferred_width (GtkCellRenderer *cell_renderer,
                                                   GtkWidget       *parent_widget,
                                                   int             *minimal_size,
                                                   int             *natural_size)
{
  gint calc_width;
  gint xpad;

  g_object_get( cell_renderer, "xpad", &xpad, NULL);

  calc_width  = (gint) xpad * 2 + FIXED_WIDTH;

  if (minimal_size)
    *minimal_size = calc_width;

  if (natural_size)
    *natural_size = calc_width;
}


/***************************************************************************
 *
 *  custom_cell_renderer_progress_get_preferred_height:
 *            crucial - calculate the size of our cell, taking into account
 *             padding and alignment properties of parent.
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_get_preferred_height (GtkCellRenderer *cell_renderer,
                                                    GtkWidget       *parent_widget,
                                                    int             *minimal_size,
                                                    int             *natural_size)
{
  gint calc_height;
  gint ypad;

  g_object_get( cell_renderer, "ypad", &ypad, NULL);

  calc_height = (gint) ypad * 2 + FIXED_HEIGHT;

  if (minimal_size)
    *minimal_size = calc_height;

  if (natural_size)
    *natural_size = calc_height;
}

/***************************************************************************
 *
 *  custom_cell_renderer_progress_render: crucial - do the rendering.
 *
 ***************************************************************************/

static void
custom_cell_renderer_progress_render(GtkCellRenderer      *cell_renderer,
                                     cairo_t              *cr,
                                     GtkWidget            *widget,
                                     const GdkRectangle   *background_area,
                                     const GdkRectangle   *cell_area,
                                     GtkCellRendererState  flags)
{
  CustomCellRendererProgress *self = CUSTOM_CELL_RENDERER_PROGRESS (cell_renderer);
  GtkStateType                state;
  gint                        width, height, x_pad, y_pad;
  gint                        x_offset, y_offset;

  custom_cell_renderer_progress_get_preferred_height (cell_renderer, widget, &height, &height);
  custom_cell_renderer_progress_get_preferred_width (cell_renderer, widget, &width, &width);

  if (gtk_widget_is_focus (widget))
    state = GTK_STATE_ACTIVE;
  else
    state = GTK_STATE_NORMAL;

  g_object_get (cell_renderer, "xpad", &x_pad, NULL);
  width -= x_pad*2;
  g_object_get (cell_renderer, "ypad", &y_pad, NULL);
  height -= y_pad*2;

  {
    GtkStyleContext *style = gtk_widget_get_style_context(widget);
    gtk_style_context_save(style);
    gtk_style_context_set_state(style, state);

    gint draw_width = cell_area->x + x_pad;
    gint draw_height = cell_area->y + y_pad;

    /* draw border */
    {
      gtk_render_frame (style, cr,
                        draw_width, draw_height,
                        width - 1, height - 1);
      /* fallback, if gtk_render_frame is now shown (as on my system) */
      gtk_render_background (style, cr,
                             draw_width, draw_height,
                             width - 1, height - 1);
    }

    /* draw progress indicator */
    {
      gint progress_width = (private (self)->progress) * (width - 1);
      gtk_style_context_set_state(style, GTK_STATE_INSENSITIVE);
      gtk_render_background (style, cr,
                             draw_width + 1, draw_height + 1,
                             progress_width - 2, height - 1 - 2);
    }

    gtk_style_context_restore(style);
  }
}


这里是一个使用我们的新 CustomCellRendererProgress 进行测试的小示例

#include "custom-cell-renderer-progressbar.h"

static GtkListStore        *liststore;

static gboolean             increasing = TRUE;   /* direction of progress bar change */

enum
{
  COL_PERCENTAGE = 0,
  COL_TEXT,
  NUM_COLS
};

#define STEP  0.01

gboolean
increase_progress_timeout (GtkCellRenderer *renderer)
{
  GtkTreeIter  iter;
  gfloat       perc = 0.0;
  gchar        buf[20];

  gtk_tree_model_get_iter_first(GTK_TREE_MODEL(liststore), &iter); /* first and only row */

  gtk_tree_model_get (GTK_TREE_MODEL(liststore), &iter, COL_PERCENTAGE, &perc, -1);

  if ( perc > (1.0-STEP)  ||  (perc < STEP && perc > 0.0) )
  {
    increasing = (!increasing);
  }

  if (increasing)
    perc = perc + STEP;
  else
    perc = perc - STEP;

  g_snprintf(buf, sizeof(buf), "%u %%", (guint)(perc*100));

  gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, perc, COL_TEXT, buf, -1);

  return TRUE; /* Call again */
}


GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  GtkTreeIter          iter;
  GtkWidget           *view;

  liststore = gtk_list_store_new(NUM_COLS, G_TYPE_FLOAT, G_TYPE_STRING);
  gtk_list_store_append(liststore, &iter);
  gtk_list_store_set (liststore, &iter, COL_PERCENTAGE, 0.5, -1); /* start at 50% */

  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore));

  g_object_unref(liststore); /* destroy store automatically with view */

  renderer = gtk_cell_renderer_text_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "text", COL_TEXT);
  gtk_tree_view_column_set_title (col, "Progress");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);

  renderer = custom_cell_renderer_progress_new();
  col = gtk_tree_view_column_new();
  gtk_tree_view_column_pack_start (col, renderer, TRUE);
  gtk_tree_view_column_add_attribute (col, renderer, "percentage", COL_PERCENTAGE);
  gtk_tree_view_column_set_title (col, "Progress");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view),col);

  g_timeout_add(50, (GSourceFunc) increase_progress_timeout, NULL);

  return view;
}


int
main (int argc, char **argv)
{
  GtkWidget *window, *view;

  gtk_init(&argc,&argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_default_size (GTK_WINDOW(window), 150, 100);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL);

  view = create_view_and_model();

  gtk_container_add(GTK_CONTAINER(window), view);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

其他人编写的单元格渲染器

[编辑 | 编辑源代码]

如果您不喜欢重新发明轮子,这里列出了一些其他人编写的自定义单元格渲染器

  • 日期单元格渲染器(规划器)(这个容易重用吗?)
  • 列表/组合单元格渲染器(规划器)(这个容易重用吗?)(FIXME: 由 GtkCellRendererCombo 弃用)
  • 弹出窗口单元格渲染器(规划器)(这个做什么?)
  • 您的自定义单元格渲染器在这里?!
华夏公益教科书