关键词:
【中文标题】HOWTO:使用 gtk (rust-gnome) 回调的惯用 Rust【英文标题】:HOWTO: Idiomatic Rust for callbacks with gtk (rust-gnome) 【发布时间】:2015-11-05 03:19:03 【问题描述】:我目前正在学习 Rust 并希望将其用于开发 GUI 基于 GTK+ 的应用程序。我的问题与注册回调有关 在这些回调中响应 GTK 事件/信号和变异状态。 我有一个可行但不优雅的解决方案,所以我想问一下是否有 是一种更简洁、更惯用的解决方案。
我已将我的代码实现为带有方法实现的结构,其中
该结构维护对 GTK 小部件的引用以及其他状态
它需要。它构造一个传递给
GtkWidget::connect*
函数为了接收事件,绘制到一个
画布等。这可能会导致借用检查器出现问题,我现在将
解释。我有一些 工作 但(恕我直言)非理想代码,我会
显示。
初始的、无效的解决方案:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::Context, RectangleInt;
struct RenderingAPITestWindow
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
impl RenderingAPITestWindow
fn new(width: i32, height: i32) -> RenderingAPITestWindow
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindowwindow: window,
drawing_area: drawing_area,
width: width,
height: height,
;
instance.drawing_area.connect_draw(|widget, cairo_context|
instance.on_draw(cairo_context);
instance.drawing_area.queue_draw();
Inhibit(true)
);
instance.drawing_area.connect_size_allocate(|widget, rect|
instance.on_size_allocate(rect);
);
instance.window.show_all();
return instance;
fn exit_on_close(&self)
self.window.connect_delete_event(|_, _|
gtk::main_quit();
Inhibit(true)
);
fn on_draw(&mut self, cairo_ctx: Context)
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
fn on_size_allocate(&mut self, rect: &RectangleInt)
self.width = rect.width as i32;
self.height = rect.height as i32;
fn main()
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: , Minor: ", gtk::get_major_version(), gtk::get_minor_version());
let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
上面的代码无法编译为闭包
RenderingAPITestWindow::new
创建并传递给调用
GtkWidget::connect*
方法尝试借用 instance
。这
编译器声明闭包可能比其中的函数寿命更长
它们被声明并且instance
归外部函数所有,
因此问题。鉴于 GTK 可能会保留对这些闭包的引用
在不确定的时间内,我们需要一种方法
生命周期可以在运行时确定,因此我的下一个问题是
RenderingAPITestWindow
实例包含在其中
Rc<RefCell<...>>
.
包装 RenderingAPITestWindow
实例编译但在运行时终止:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::Context, RectangleInt;
struct RenderingAPITestWindow
window: gtk::Window,
drawing_area: gtk::DrawingArea,
width: i32,
height: i32
impl RenderingAPITestWindow
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>>
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = RenderingAPITestWindowwindow: window,
drawing_area: drawing_area,
width: width,
height: height,
;
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context|
wrapped_instance_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
);
let wrapped_instance_for_sizealloc = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect|
wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);
);
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
fn exit_on_close(&self)
self.window.connect_delete_event(|_, _|
gtk::main_quit();
Inhibit(true)
);
fn on_draw(&mut self, cairo_ctx: Context)
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
fn on_size_allocate(&mut self, rect: &RectangleInt)
self.width = rect.width as i32;
self.height = rect.height as i32;
fn main()
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: , Minor: ", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
上述解决方案可以编译,但不是特别漂亮:
RenderingAPITestWindow::new
返回一个
Rc<RefCell<RenderingAPITestWindow>>
而不是
RenderingAPITestWindow
访问RenderingAPITestWindow
的字段和方法很复杂
由于Rc<RefCell<...>>
必须打开;现在需要
wrapped_instance.borrow().some_method(...)
而不仅仅是
instance.some_method(...)
每个闭包都需要它自己的wrapped_instance
的克隆;尝试
使用wrapped_instance
会尝试借用一个对象——
这次是包装器而不是RenderingAPITestWindow
——也就是说
RenderingAPITestWindow::new
和以前一样拥有
虽然上述编译,但它在运行时死亡:
thread '<main>' panicked at 'RefCell<T> already borrowed', ../src/libcore/cell.rs:442
An unknown error occurred
这是由于对window.show_all()
的调用导致 GTK
初始化小部件层次结构,生成绘图区小部件
接收size-allocate
事件。访问窗口调用
show_all()
要求打开 Rc<RefCell<...>>
(因此
wrapped_instance.borrow().window.show_all();
) 和实例
借来的。在show_all()
返回时借用结束之前,GTK 调用
绘图区的size-allocate
事件处理程序,导致关闭
连接到它(上面的 4 行)被调用。关闭试图
借用对RenderingAPITestWindow
实例的可变引用
(wrapped_instance_for_sizealloc.borrow_mut().on_size_allocate(rect);
)
为了调用on_size_allocate
方法。这试图借用一个
可变引用,而第一个不可变引用仍在范围内。
第二次借用会导致运行时恐慌。
工作但是 - 恕我直言 - 我设法得到的不优雅的解决方案
到目前为止的工作是将RenderingAPITestWindow
拆分为两个结构,其中
由回调修改的可变状态移动到
单独的结构。
拆分 RenderingAPITestWindow
结构的可行但不优雅的解决方案:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::Context, RectangleInt;
struct RenderingAPITestWindowState
width: i32,
height: i32
impl RenderingAPITestWindowState
fn new(width: i32, height: i32) -> RenderingAPITestWindowState
return RenderingAPITestWindowStatewidth: width, height: height;
fn on_draw(&mut self, cairo_ctx: Context)
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
fn on_size_allocate(&mut self, rect: &RectangleInt)
self.width = rect.width as i32;
self.height = rect.height as i32;
struct RenderingAPITestWindow
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: Rc<RefCell<RenderingAPITestWindowState>>
impl RenderingAPITestWindow
fn new(width: i32, height: i32) -> Rc<RefCell<RenderingAPITestWindow>>
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let wrapped_state = Rc::new(RefCell::new(RenderingAPITestWindowState::new(width, height)))
;
let instance = RenderingAPITestWindowwindow: window,
drawing_area: drawing_area,
state: wrapped_state.clone()
;
let wrapped_instance = Rc::new(RefCell::new(instance));
let wrapped_state_for_draw = wrapped_state.clone();
let wrapped_instance_for_draw = wrapped_instance.clone();
wrapped_instance.borrow().drawing_area.connect_draw(move |widget, cairo_context|
wrapped_state_for_draw.borrow_mut().on_draw(cairo_context);
wrapped_instance_for_draw.borrow().drawing_area.queue_draw();
Inhibit(true)
);
let wrapped_state_for_sizealloc = wrapped_state.clone();
wrapped_instance.borrow().drawing_area.connect_size_allocate(move |widget, rect|
wrapped_state_for_sizealloc.borrow_mut().on_size_allocate(rect);
);
wrapped_instance.borrow().window.show_all();
return wrapped_instance;
fn exit_on_close(&self)
self.window.connect_delete_event(|_, _|
gtk::main_quit();
Inhibit(true)
);
fn main()
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: , Minor: ", gtk::get_major_version(), gtk::get_minor_version());
let wrapped_window = RenderingAPITestWindow::new(800, 500);
wrapped_window.borrow().exit_on_close();
gtk::main();
虽然上面的代码可以按要求工作,但我想找到一个更好的方法
为前进;我想问是否有人知道更好的方法
以上使编程过程相当复杂,需要
使用 Rc<RefCell<...>>
并拆分结构以满足 Rust 的借用规则。
【问题讨论】:
由于自动取消引用,我很确定(*val.borrow())
和(*val.borrow_mut())
的所有实例都可以替换为val.borrow()
和val.borrow_mut()
。
“鉴于 GTK 可能会在未指定的时间内保留对这些闭包的引用” --- 对我来说,这是您需要使用的提示 @987654361 @如果多个闭包需要访问内存中的相同位置。
感谢您的提示; (*val.borrow())
的出现已被 val.borrow()
替换。这让事情变得更漂亮了! :)
【参考方案1】:
这是我想出的工作版本:
#![cfg_attr(not(feature = "gtk_3_10"), allow(unused_variables, unused_mut))]
extern crate gtk;
extern crate cairo;
use std::rc::Rc;
use std::cell::RefCell;
use gtk::traits::*;
use gtk::signal::Inhibit;
use cairo::Context, RectangleInt;
struct RenderingAPITestWindow
window: gtk::Window,
drawing_area: gtk::DrawingArea,
state: RefCell<RenderingState>,
struct RenderingState
width: i32,
height: i32,
impl RenderingAPITestWindow
fn new(width: i32, height: i32) -> Rc<RenderingAPITestWindow>
let window = gtk::Window::new(gtk::WindowType::TopLevel).unwrap();
let drawing_area = gtk::DrawingArea::new().unwrap();
drawing_area.set_size_request(width, height);
window.set_title("Cairo API test");
window.add(&drawing_area);
let instance = Rc::new(RenderingAPITestWindow
window: window,
drawing_area: drawing_area,
state: RefCell::new(RenderingState
width: width,
height: height,
),
);
let instance2 = instance.clone();
instance.drawing_area.connect_draw(move |widget, cairo_context|
instance2.state.borrow().on_draw(cairo_context);
instance2.drawing_area.queue_draw();
Inhibit(true)
);
let instance2 = instance.clone();
instance.drawing_area.connect_size_allocate(move |widget, rect|
instance2.state.borrow_mut().on_size_allocate(rect);
);
instance.window.show_all();
instance
fn exit_on_close(&self)
self.window.connect_delete_event(|_, _|
gtk::main_quit();
Inhibit(true)
);
impl RenderingState
fn on_draw(&self, cairo_ctx: Context)
cairo_ctx.save();
cairo_ctx.move_to(50.0, (self.height as f64) * 0.5);
cairo_ctx.set_font_size(18.0);
cairo_ctx.show_text("The only curse they could afford to put on a tomb these days was 'Bugger Off'. --PTerry");
cairo_ctx.restore();
fn on_size_allocate(&mut self, rect: &RectangleInt)
self.width = rect.width as i32;
self.height = rect.height as i32;
fn main()
gtk::init().unwrap_or_else(|_| panic!("Failed to initialize GTK."));
println!("Major: , Minor: ", gtk::get_major_version(), gtk::get_minor_version());
let window = RenderingAPITestWindow::new(800, 500);
window.exit_on_close();
gtk::main();
我通过一些观察得出了这个结论:
实例在多个闭包之间共享不确定的时间。Rc
是该场景的正确答案,因为它提供共享所有权。 Rc
使用起来非常符合人体工程学;它与任何其他指针类型一样工作。
instance
中唯一实际发生突变的部分是您的状态。由于您的实例是共享的,因此不能使用标准的&mut
指针可变地借用它。因此,您必须使用内部可变性。这就是RefCell
提供的。但请注意,您只需要在您正在变异的状态上使用RefCell
。所以这仍然将状态分离到一个单独的结构中,但它在 IMO 中运行良好。
对此代码的一个可能修改是将#[derive(Clone, Copy)]
添加到RenderingState
结构的定义中。由于它可以是Copy
(因为它的所有组件类型都是Copy
),你可以使用Cell
而不是RefCell
。
【讨论】:
谢谢!这显着改善了问题! :) @GeoffFrench 看起来您是 *** 的新手,但如果您认为我已经充分回答了您的问题,那么您应该将其标记为已接受。 :-) 谢谢!使用gtk+播放视频
关于如何使用GTK+播放视频的任何建议?问候,LancyNorbertFernandes答案对于在GTK+和其他GTKBindings上播放视频,您有很多选择。选项:使用第三方库1-尝试使用ogmrip-gtk,一组Gtk接口,它允许您将开源OGMRip库用作Gtk-Widget。2-您可以使用... 查看详情
“将大小分配给...”在 Gtk.ScrolledWindow 中使用 Gtk.TreeView 时出现 GTK 警告
】“将大小分配给...”在Gtk.ScrolledWindow中使用Gtk.TreeView时出现GTK警告【英文标题】:"Allocatingsizeto..."GTKWarningwhenusingGtk.TreeViewinsideGtk.ScrolledWindow【发布时间】:2018-02-2511:11:55【问题描述】:我在GTK3应用程序中收到以下警告... 查看详情
GTK+ 3.0:如何将 Gtk.TreeStore 与自定义模型项一起使用?
】GTK+3.0:如何将Gtk.TreeStore与自定义模型项一起使用?【英文标题】:GTK+3.0:HowtouseaGtk.TreeStorewithcustommodelitems?【发布时间】:2012-06-2601:59:57【问题描述】:我正在尝试用Python开发一个GTK应用程序,但我真的不知道如何正确使用gtk.... 查看详情
windows下安装并使用gtk4(代码片段)
文章目录Windows下安装并使用GTK4第一步安装Mingw第二步安装GTK4第三步使用GTK4Windows下安装并使用GTK4 前一段时间撰写了一篇关于如何在Ubuntu下安装GTK4的文章《Ubuntu20.04LTS(amd64)下安装GTK4》,现在介绍如何在Windows下安装... 查看详情
windows下安装并使用gtk4(代码片段)
文章目录Windows下安装并使用GTK4第一步安装Mingw第二步安装GTK4第三步使用GTK4Windows下安装并使用GTK4 前一段时间撰写了一篇关于如何在Ubuntu下安装GTK4的文章《Ubuntu20.04LTS(amd64)下安装GTK4》,现在介绍如何在Windows下安装... 查看详情
在 gtkmm3 中使用 Gtk::Assistant
】在gtkmm3中使用Gtk::Assistant【英文标题】:UsingGtk::Assistantingtkmm3【发布时间】:2021-07-0910:01:49【问题描述】:我有一个gtkmm3应用程序,我计划在其中使用派生自Gtk::Assistant的类来执行一些用户配置。由于Gtk::Assistant派生自Gtk::Window... 查看详情
使用 OpenGL 编译 GTK+
】使用OpenGL编译GTK+【英文标题】:CompilationforGTK+usingOpenGL【发布时间】:2016-12-0313:15:51【问题描述】:上下文:我正在学习使用GTK+开发GUI。我还想在GUI上画线和圆。所以我从教程开始,我被GtkGLArea的部分困住了。我正在遵循GTK+d... 查看详情
如何使用 Gtk.Application Gtk+3 Glade Python 更改/切换窗口?
】如何使用Gtk.ApplicationGtk+3GladePython更改/切换窗口?【英文标题】:Howtochange/switchwindowsusingGtk.ApplicationGtk+3GladePython?【发布时间】:2018-01-0708:22:34【问题描述】:我阅读了本教程-https://python-gtk-3-tutorial.readthedocs.io/en/latest/application. 查看详情
将 cairo 与 gtk3 一起使用
】将cairo与gtk3一起使用【英文标题】:usingcairowithgtk3【发布时间】:2012-01-0404:41:40【问题描述】:我正在尝试在gtk3中使用cairo绘制散点图。首先,我在这里使用示例:http://zetcode.com/tutorials/cairographicstutorial/他们用gtk2编译成功,... 查看详情
gtk3+gtkprogressbar使用(代码片段)
#include<gtk/gtk.h>gbooleantimeout_callback(gpointerdata)gdoublevalue;GString*text;value=gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(data));value+=0.01;if(value>1.0)value=0.0;gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(data),value);text=g_string_new(gtk_progress_bar_get_text(GTK_PROGR... 查看详情
Howto:对用户创建的 shell 脚本使用 syslog
】Howto:对用户创建的shell脚本使用syslog【英文标题】:Howto:Usingsyslogforusercreatedshellscript【发布时间】:2011-07-1106:07:14【问题描述】:关于syslog的信息比比皆是,但我找不到任何对我感兴趣的非常简洁的信息。我有一个用户创建... 查看详情
mac上使用clion基于cmake开发gtk
前提:已安装好gcc,make,cmake,clion,g++等gtk无关东西1.安装gtk,brewinstallgtk+ brewinstallgtk+32.新建一个工程代码如下#include<stdio.h>#include<gtk-3.0/gtk/gtk.h>staticvoidactivate(GtkApplication*app,gpointerus 查看详情
是否可以将 GTK+ 与 C++ 一起使用?
】是否可以将GTK+与C++一起使用?【英文标题】:IsitpossibletouseGTK+withC++?【发布时间】:2011-04-1103:00:33【问题描述】:我正在选择一个用于C++学习的GUI工具包。我在网上做了一些搜索,大多数人建议GTKmm用于C++而不是GTK+。尽管如此... 查看详情
如何使用本机寡妇 api 移动 Gtk::Window?
】如何使用本机寡妇api移动Gtk::Window?【英文标题】:HowtomoveaGtk::Windowwithnativewidowsapi?【发布时间】:2021-01-1415:32:17【问题描述】:背景我正在使用Gtkmm4用C++开发一个应用程序。我正在创建一个Gtk::Window。现在我想将此窗口移动到... 查看详情
windows下安装gtk并使用gcc编译?
】windows下安装gtk并使用gcc编译?【英文标题】:Installinggtkandcompilingusinggccunderwindows?【发布时间】:2009-09-2006:30:12【问题描述】:我在c:/programfiles中安装了gcc(也设置为路径变量),并且我拥有来自http://www.gtk.org/download-windows.html... 查看详情
使用 Gtk::Viewport (gtkmm3) 直接滚动
】使用Gtk::Viewport(gtkmm3)直接滚动【英文标题】:DirectscrollingusingGtk::Viewport(gtkmm3)【发布时间】:2021-01-2004:40:09【问题描述】:我有一个自定义容器,它通过在Gtk::Viewport中嵌入Gtk::Grid来提供滚动功能。滚动部分工作完美,除了一个... 查看详情
使用gtk获取关键信息
/*! *riefGTKProgramtogetinformationaboutkeypressed *authorFabienArcellier * *TocompilwithGTK:gcc-Wall`pkg-config--cflagsgtk+-2.0``pkg-config--libsgtk+-2.0`-okey_tool-gkey_tool.c */#include<gtk/gtk.h> staticvoiddestroy(GtkWidget*,gpointer);staticgbooleanke... 查看详情
如何使用 GTK 绘制像素图?
】如何使用GTK绘制像素图?【英文标题】:HowdoIdrawaPixmapwithGTK?【发布时间】:2021-04-1806:09:25【问题描述】:使用GTK3,我一直在尝试从内存缓冲区中绘制像素图。我刚刚创建了一个内存缓冲区,并用32位RGBA格式的交替颜色行填充... 查看详情