You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将GTK4+C语言代码适配为GtkCustomLayout布局管理器?

如何将GtkConstraintLayout适配为GtkCustomLayout?

我正在尝试GTK4的各类布局管理器,已成功测试GtkBinLayout、GtkBoxLayout、GtkCenterLayout以及GtkConstraintLayout(代码如下),但在使用GtkCustomLayout时无法推进。请问如何将以下代码适配为使用GtkCustomLayout布局管理器?

原代码:

#include "my_widget.h"

struct _MyWidget
{
 GtkWidget parent_instance;
 GtkWidget *label;
 GtkWidget *entry;
 GtkWidget *button;
};

struct _MyWidgetClass
{
 GtkWidgetClass parent_class;
};

G_DEFINE_TYPE (MyWidget, my_widget, GTK_TYPE_WIDGET)
   
//Clear the template children when disposing the widget instance
static void
my_widget_dispose (GObject *gobject)
{
 gtk_widget_dispose_template (GTK_WIDGET (gobject), MY_TYPE_WIDGET);

 G_OBJECT_CLASS (my_widget_parent_class)->dispose (gobject);
}

static void
build_constraints (MyWidget *self, GtkConstraintLayout *manager)
{
   //Width of the label
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new_constant(GTK_CONSTRAINT_TARGET (MY_WIDGET(self)->label),
            GTK_CONSTRAINT_ATTRIBUTE_WIDTH,GTK_CONSTRAINT_RELATION_EQ,400.0,
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  // 400
   // Height of the label
       gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new_constant(GTK_CONSTRAINT_TARGET (MY_WIDGET(self)->label),
            GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,GTK_CONSTRAINT_RELATION_EQ,50.0,
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  // 50
   //Width of Entry
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new_constant(GTK_CONSTRAINT_TARGET (MY_WIDGET(self)->entry),
            GTK_CONSTRAINT_ATTRIBUTE_WIDTH,GTK_CONSTRAINT_RELATION_EQ,300.0,
        GTK_CONSTRAINT_STRENGTH_REQUIRED));   //  300
   // Label's starting position
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(NULL, GTK_CONSTRAINT_ATTRIBUTE_START,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->label, GTK_CONSTRAINT_ATTRIBUTE_START,1.0, -10.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  // Starting edges (left)
   //Horizontal Label Position
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(NULL, GTK_CONSTRAINT_ATTRIBUTE_TOP,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->label, GTK_CONSTRAINT_ATTRIBUTE_TOP,1.0, -10.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  
       // Horizontal Entry Position
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(MY_WIDGET(self)->label, GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->entry, GTK_CONSTRAINT_ATTRIBUTE_TOP,1.0, 0.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  
   // Entry starting position
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(NULL, GTK_CONSTRAINT_ATTRIBUTE_START,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->entry, GTK_CONSTRAINT_ATTRIBUTE_START,1.0, -10.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  //  Starting edges (left)
   // Horizontal position of the button
   gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(MY_WIDGET(self)->label, GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->button, GTK_CONSTRAINT_ATTRIBUTE_TOP,1.0, 0.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  
   // Starting position of the button  
       gtk_constraint_layout_add_constraint (manager,
      gtk_constraint_new(MY_WIDGET(self)->entry, GTK_CONSTRAINT_ATTRIBUTE_END,GTK_CONSTRAINT_RELATION_EQ,
            MY_WIDGET(self)->button, GTK_CONSTRAINT_ATTRIBUTE_START,1.0, -5.0, 
        GTK_CONSTRAINT_STRENGTH_REQUIRED));  // Starting edges (left)
}

static void
my_widget_init (MyWidget *self)
{
  GtkLayoutManager *constraint_layout;
  constraint_layout = gtk_widget_get_layout_manager (GTK_WIDGET (self));
  gtk_widget_init_template (GTK_WIDGET (self));
  build_constraints (self, GTK_CONSTRAINT_LAYOUT(constraint_layout));
}

static void
my_widget_class_init (MyWidgetClass *class)
{
 G_OBJECT_CLASS (class)->dispose = my_widget_dispose;
 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

 gtk_widget_class_set_template_from_resource (widget_class,"/Prog/widget.ui");

 //Bind the widgets defined inside the template file to the corresponding 
 //members of the widget’s instance data structure
 gtk_widget_class_bind_template_child (widget_class, MyWidget, entry);
 gtk_widget_class_bind_template_child (widget_class, MyWidget, button);
 gtk_widget_class_bind_template_child (widget_class, MyWidget, label);

 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CONSTRAINT_LAYOUT); 
}

GtkWidget *
my_widget_new ()
{
 MyWidget *self;
 self = g_object_new(MY_TYPE_WIDGET,NULL);
 return GTK_WIDGET (self);
}

/****************************************************************/

static void activate (GtkApplication *app, gpointer user_data)
{
   GtkWidget *window;
   window = gtk_application_window_new(app);
   gtk_window_set_resizable (GTK_WINDOW( window),FALSE);
   GtkWidget * widget = my_widget_new();
   gtk_window_set_child(GTK_WINDOW(window),GTK_WIDGET(widget));
   gtk_window_present(GTK_WINDOW (window));
}

int main (int argc, char **argv)
{
   GtkApplication *app;
   int status;

   app = gtk_application_new ("org.gtk.mywidget", G_APPLICATION_DEFAULT_FLAGS);
   g_signal_connect(app, "activate",G_CALLBACK(activate),NULL);
   status = g_application_run (G_APPLICATION(app), argc, argv);
   g_object_unref(app);
   return status;
}

原界面效果:
原界面效果


适配GtkCustomLayout的解决方案

GtkCustomLayout要求手动实现**测量(measure)和分配(allocate)**逻辑,替代原有的约束规则。以下是修改后的完整代码,关键改动已标注:

#include "my_widget.h"

struct _MyWidget
{
 GtkWidget parent_instance;
 GtkWidget *label;
 GtkWidget *entry;
 GtkWidget *button;
};

struct _MyWidgetClass
{
 GtkWidgetClass parent_class;
};

G_DEFINE_TYPE (MyWidget, my_widget, GTK_TYPE_WIDGET)

// 自定义测量回调:计算控件所需的最小/自然尺寸
static void
my_widget_measure (GtkCustomLayout       *layout,
                   GtkWidget             *widget,
                   GtkOrientation         orientation,
                   int                    for_size,
                   int                   *minimum,
                   int                   *natural,
                   int                   *minimum_baseline,
                   int                   *natural_baseline)
{
  MyWidget *self = MY_WIDGET(widget);
  int label_min, label_nat;
  int entry_min, entry_nat;
  int button_min, button_nat;

  // 测量每个子控件的尺寸
  gtk_widget_measure(self->label, orientation, for_size, &label_min, &label_nat, NULL, NULL);
  gtk_widget_measure(self->entry, orientation, for_size, &entry_min, &entry_nat, NULL, NULL);
  gtk_widget_measure(self->button, orientation, for_size, &button_min, &button_nat, NULL, NULL);

  if (orientation == GTK_ORIENTATION_HORIZONTAL) {
    // 水平方向:取最宽的子控件(label的400)加上边距
    *minimum = 400 + 20; // 左右各10边距
    *natural = 400 + 20;
  } else {
    // 垂直方向:label高度+entry高度+button高度+间距
    *minimum = 50 + 30 + 30 + 20; // label(50) + entry(默认30) + button(默认30) + 上下10边距
    *natural = 50 + 30 + 30 + 20;
  }
}

// 自定义分配回调:手动设置每个子控件的位置和大小
static void
my_widget_allocate (GtkCustomLayout       *layout,
                   GtkWidget             *widget,
                   int                    width,
                   int                    height,
                   int                    baseline)
{
  MyWidget *self = MY_WIDGET(widget);
  GtkAllocation alloc;

  // 1. 分配Label:固定宽400、高50,位置(10,10)
  alloc.x = 10;
  alloc.y = 10;
  alloc.width = 400;
  alloc.height = 50;
  gtk_widget_allocate(self->label, &alloc, baseline);

  // 2. 分配Entry:固定宽300,位置(10, 10+50)=60,高度用自然尺寸
  int entry_nat_h;
  gtk_widget_measure(self->entry, GTK_ORIENTATION_VERTICAL, -1, NULL, &entry_nat_h, NULL, NULL);
  alloc.x = 10;
  alloc.y = 10 + 50;
  alloc.width = 300;
  alloc.height = entry_nat_h;
  gtk_widget_allocate(self->entry, &alloc, baseline);

  // 3. 分配Button:位置在Entry右侧(Entry.x + Entry.width -5),垂直和Entry对齐
  int button_nat_w, button_nat_h;
  gtk_widget_measure(self->button, GTK_ORIENTATION_HORIZONTAL, -1, NULL, &button_nat_w, NULL, NULL);
  gtk_widget_measure(self->button, GTK_ORIENTATION_VERTICAL, -1, NULL, &button_nat_h, NULL, NULL);
  alloc.x = 10 + 300 - 5; // Entry右侧往左偏移5像素
  alloc.y = 10 + 50;
  alloc.width = button_nat_w;
  alloc.height = button_nat_h;
  gtk_widget_allocate(self->button, &alloc, baseline);
}

static void
my_widget_dispose (GObject *gobject)
{
 gtk_widget_dispose_template (GTK_WIDGET (gobject), MY_TYPE_WIDGET);
 G_OBJECT_CLASS (my_widget_parent_class)->dispose (gobject);
}

static void
my_widget_init (MyWidget *self)
{
  GtkLayoutManager *custom_layout = gtk_widget_get_layout_manager(GTK_WIDGET(self));
  gtk_widget_init_template(GTK_WIDGET(self));
  
  // 为CustomLayout设置测量和分配回调
  gtk_custom_layout_set_callbacks(GTK_CUSTOM_LAYOUT(custom_layout),
                                  my_widget_measure,
                                  my_widget_allocate,
                                  NULL, // 销毁回调(可选)
                                  NULL); // 用户数据
}

static void
my_widget_class_init (MyWidgetClass *class)
{
 G_OBJECT_CLASS (class)->dispose = my_widget_dispose;
 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

 gtk_widget_class_set_template_from_resource (widget_class,"/Prog/widget.ui");

 gtk_widget_class_bind_template_child (widget_class, MyWidget, entry);
 gtk_widget_class_bind_template_child (widget_class, MyWidget, button);
 gtk_widget_class_bind_template_child (widget_class, MyWidget, label);

 // 替换布局管理器类型为GtkCustomLayout
 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CUSTOM_LAYOUT); 
}

GtkWidget *
my_widget_new ()
{
 MyWidget *self = g_object_new(MY_TYPE_WIDGET,NULL);
 return GTK_WIDGET (self);
}

/****************************************************************/

static void activate (GtkApplication *app, gpointer user_data)
{
   GtkWidget *window = gtk_application_window_new(app);
   gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
   GtkWidget *widget = my_widget_new();
   gtk_window_set_child(GTK_WINDOW(window), widget);
   gtk_window_present(GTK_WINDOW(window));
}

int main (int argc, char **argv)
{
   GtkApplication *app = gtk_application_new("org.gtk.mywidget", G_APPLICATION_DEFAULT_FLAGS);
   g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
   int status = g_application_run(G_APPLICATION(app), argc, argv);
   g_object_unref(app);
   return status;
}

关键改动说明

  1. 替换布局管理器类型:将gtk_widget_class_set_layout_manager_type的参数改为GTK_TYPE_CUSTOM_LAYOUT
  2. 实现测量回调:my_widget_measure计算整个自定义控件的最小和自然尺寸,匹配原约束的整体布局大小
  3. 实现分配回调:my_widget_allocate手动给每个子控件设置位置和大小,完全复现原约束的布局规则:
    • Label固定宽400、高50,左上角(10,10)
    • Entry宽300,位于Label正下方,左边距10
    • Button位于Entry右侧,与Entry垂直对齐,间距5像素
  4. 移除约束相关代码:删除build_constraints函数及其调用,不再依赖约束系统

内容的提问来源于stack exchange,提问作者Holger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 09:02:03