如何将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; }
关键改动说明
- 替换布局管理器类型:将
gtk_widget_class_set_layout_manager_type的参数改为GTK_TYPE_CUSTOM_LAYOUT - 实现测量回调:
my_widget_measure计算整个自定义控件的最小和自然尺寸,匹配原约束的整体布局大小 - 实现分配回调:
my_widget_allocate手动给每个子控件设置位置和大小,完全复现原约束的布局规则:- Label固定宽400、高50,左上角(10,10)
- Entry宽300,位于Label正下方,左边距10
- Button位于Entry右侧,与Entry垂直对齐,间距5像素
- 移除约束相关代码:删除
build_constraints函数及其调用,不再依赖约束系统
内容的提问来源于stack exchange,提问作者Holger
相关产品推荐
相关产品推荐

