使用 GTK4 进行拖放:通过 ContentProvider 连接 DragSource 和 DropTarget 以获得派生 类
Drag and drop with GTK4: connecting DragSource and DropTarget via ContentProvider for derived classes
我正在研究 (py)gtk4 的拖放功能,但遇到了困难。我有一个流框派生的 class MediaGallery
,其中包含带有图像及其文件名的帧(class MediaFile
),以及一个列表框派生的 class Albums
.我想将一个或多个选定的图像从 MediaGallery
拖到 Albums
,这最终会将它们添加到基础数据库中。
相关代码:
class Album(gtk.Box):
def __init__(self, *args, **kwargs):
super().__init__(*args, orientation=gtk.Orientation.HORIZONTAL, **kwargs)
self.label = gtk.Label(hexpand=False)
self.append(self.label)
self.label.set_visible(False)
self.entry = gtk.Entry()
self.append(self.entry)
self.entry.set_visible(True)
self.entry.connect('activate', self.on_entry_changed)
dnd = gtk.DropTarget.new(gdk.FileList, gdk.DragAction.COPY)
dnd.connect('drop', self.on_dnd_drop)
dnd.connect('accept', self.on_dnd_accept)
dnd.connect('enter', self.on_dnd_enter)
dnd.connect('motion', self.on_dnd_motion)
dnd.connect('leave', self.on_dnd_leave)
self.add_controller(dnd)
def on_entry_changed(self, entry):
name = entry.get_text()
self.label.set_text(name)
self.entry.set_visible(False)
self.label.set_visible(True)
def on_dnd_drop(self, value, x, y, user_data):
print(f'in on_dnd_drop(); value={value}, x={x}, y={y}, user_data={user_data}')
def on_dnd_accept(self, drop, user_data):
print(f'in on_dnd_accept(); drop={drop}, user_data={user_data}')
return True
def on_dnd_enter(self, drop_target, x, y):
print(f'in on_dnd_enter(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_motion(self, drop_target, x, y):
print(f'in on_dnd_motion(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_leave(self, user_data):
print(f'in on_dnd_leave(); user_data={user_data}')
class MediaFile(gtk.FlowBoxChild):
def __init__(self, *args, file, **kwargs):
super().__init__(*args, **kwargs)
self.filename = file
frame = gtk.Frame()
self.set_child(frame)
vbox = gtk.Box(orientation=gtk.Orientation.VERTICAL)
frame.set_child(vbox)
self.image = gtk.Image.new_from_file(file)
self.image.set_pixel_size(256)
vbox.append(self.image)
label = gtk.Label.new(file[file.rfind('/')+1:])
vbox.append(label)
def __repr__(self):
return f'<MediaFile {self.filename}>'
class MediaGallery(gtk.FlowBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = kwargs.get('name', 'default')
self.connect('child-activated', self.on_media_selected)
dnd = gtk.DragSource.new()
dnd.set_actions(gdk.DragAction.COPY)
dnd.connect('prepare', self.on_dnd_prepare)
dnd.connect('drag-begin', self.on_dnd_begin)
dnd.connect('drag-end', self.on_dnd_end)
self.add_controller(dnd)
def __repr__(self):
return f'<MediaGallery {self.name}>'
def on_media_selected(self, gallery, media_file):
print(f'on_media_selected(); gallery={gallery}, media_file={media_file}')
def on_dnd_prepare(self, drag_source, x, y):
data = self.get_selected_children()
print(f'in on_dnd_prepare(); drag_source={drag_source}, x={x}, y={y}, data={data}')
if len(data) == 0:
return None
paintable = data[0].image.get_paintable()
drag_image = gtk.Image.new_from_paintable(paintable)
drag_image.set_opacity(0.5) # FIXME: not sure why transparency doesn't work
drag_source.set_icon(drag_image.get_paintable(), 128, 128) # FIXME: not sure why hot_x and hot_y don't work
content = gdk.ContentProvider.new_for_value(data)
return content
def on_dnd_begin(self, drag_source, data):
content = data.get_content()
print(f'in on_dnd_begin(); drag_source={drag_source}, data={data}, content={content}')
def on_dnd_end(self, drag, drag_data, flag):
print(f'in on_dnd_end(); drag={drag}, drag_data={drag_data}, flag={flag}')
完整代码可从 here 获得。我得到的输出:
in on_dnd_prepare(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, x=176.15234375, y=172.96092224121094, data=[<MediaFile 20210712_190722A.jpg>]
in on_dnd_begin(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, data=<__gi__.GdkWaylandDrag object at 0x7f75424f2e00 (GdkWaylandDrag at 0x41b6ac0)>, content=<__gi__.GdkContentProviderValue object at 0x7f75424eb040 (GdkContentProviderValue at 0x43a8c10)>
in on_dnd_accept(); drop=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, user_data=<__gi__.GdkWaylandDrop object at 0x7f7542374440 (GdkWaylandDrop at 0x7f7560157390)>
in on_dnd_enter(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gtype.c:4322: type id '0' is invalid
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: cant peek value table for type '<invalid>' which is not currently referenced
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gvalue.c:185: cannot initialize GValue with type '(null)', this type has no GTypeValueTable implementation
return Gio.Application.run(self, *args, **kwargs)
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): GLib-GIO-CRITICAL **: 22:21:40.071: g_task_return_error: assertion 'error != NULL' failed
in on_dnd_leave(); user_data=<Gtk.DropTarget object at 0x7f7542374480 (GtkDropTarget at 0x3f56aa0)>
我找不到任何关于如何为 DropTarget
适当设置 gdk
类型以及如何使 DragSource
和 DropTarget
通过 [= 交换数据的文档22=]。任何人都可以提供任何见解吗?提前致谢!
你实际上应该 return GObject.Value 在 ::prepare
例如,这里更改为使用 gliststore 将多个项目存储在单个 GObject 中,并从中创建一个 GValue:
def on_dnd_prepare(self, drag_source, x, y):
data = gio.ListStore()
data.splice(0, 0, self.get_selected_children())
print(data.get_n_items())
print(f'in on_dnd_prepare(); drag_source={drag_source}, x={x}, y={y}, data={data}')
if len(data) == 0:
return None
paintable = data[0].image.get_paintable() # TODO: make this nicer for multiple selections
drag_image = gtk.Image.new_from_paintable(paintable)
drag_image.set_opacity(0.5) # FIXME: not sure why transparency doesn't work
drag_source.set_icon(drag_image.get_paintable(), 128, 128) # FIXME: not sure why hot_x and hot_y don't work
content = gdk.ContentProvider.new_for_value(gobject.Value(gio.ListModel, data))
return content
您应该让放置目标接受正确的类型(GdkFileList 在 PyGObject 中损坏 https://gitlab.gnome.org/GNOME/pygobject/-/issues/468)
dnd = gtk.DropTarget.new(gio.ListModel, gdk.DragAction.COPY)
并且你应该修正 drop 函数的参数
def on_dnd_drop(self, drop_target, value, x, y):
print(f'in on_dnd_drop(); value={value}, x={x}, y={y}')
print(list(value))
您可以在此处访问创建 gobject.Value 时传递的内容作为值参数(在本例中为 MediaFiles 列表)
我正在研究 (py)gtk4 的拖放功能,但遇到了困难。我有一个流框派生的 class MediaGallery
,其中包含带有图像及其文件名的帧(class MediaFile
),以及一个列表框派生的 class Albums
.我想将一个或多个选定的图像从 MediaGallery
拖到 Albums
,这最终会将它们添加到基础数据库中。
相关代码:
class Album(gtk.Box):
def __init__(self, *args, **kwargs):
super().__init__(*args, orientation=gtk.Orientation.HORIZONTAL, **kwargs)
self.label = gtk.Label(hexpand=False)
self.append(self.label)
self.label.set_visible(False)
self.entry = gtk.Entry()
self.append(self.entry)
self.entry.set_visible(True)
self.entry.connect('activate', self.on_entry_changed)
dnd = gtk.DropTarget.new(gdk.FileList, gdk.DragAction.COPY)
dnd.connect('drop', self.on_dnd_drop)
dnd.connect('accept', self.on_dnd_accept)
dnd.connect('enter', self.on_dnd_enter)
dnd.connect('motion', self.on_dnd_motion)
dnd.connect('leave', self.on_dnd_leave)
self.add_controller(dnd)
def on_entry_changed(self, entry):
name = entry.get_text()
self.label.set_text(name)
self.entry.set_visible(False)
self.label.set_visible(True)
def on_dnd_drop(self, value, x, y, user_data):
print(f'in on_dnd_drop(); value={value}, x={x}, y={y}, user_data={user_data}')
def on_dnd_accept(self, drop, user_data):
print(f'in on_dnd_accept(); drop={drop}, user_data={user_data}')
return True
def on_dnd_enter(self, drop_target, x, y):
print(f'in on_dnd_enter(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_motion(self, drop_target, x, y):
print(f'in on_dnd_motion(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_leave(self, user_data):
print(f'in on_dnd_leave(); user_data={user_data}')
class MediaFile(gtk.FlowBoxChild):
def __init__(self, *args, file, **kwargs):
super().__init__(*args, **kwargs)
self.filename = file
frame = gtk.Frame()
self.set_child(frame)
vbox = gtk.Box(orientation=gtk.Orientation.VERTICAL)
frame.set_child(vbox)
self.image = gtk.Image.new_from_file(file)
self.image.set_pixel_size(256)
vbox.append(self.image)
label = gtk.Label.new(file[file.rfind('/')+1:])
vbox.append(label)
def __repr__(self):
return f'<MediaFile {self.filename}>'
class MediaGallery(gtk.FlowBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = kwargs.get('name', 'default')
self.connect('child-activated', self.on_media_selected)
dnd = gtk.DragSource.new()
dnd.set_actions(gdk.DragAction.COPY)
dnd.connect('prepare', self.on_dnd_prepare)
dnd.connect('drag-begin', self.on_dnd_begin)
dnd.connect('drag-end', self.on_dnd_end)
self.add_controller(dnd)
def __repr__(self):
return f'<MediaGallery {self.name}>'
def on_media_selected(self, gallery, media_file):
print(f'on_media_selected(); gallery={gallery}, media_file={media_file}')
def on_dnd_prepare(self, drag_source, x, y):
data = self.get_selected_children()
print(f'in on_dnd_prepare(); drag_source={drag_source}, x={x}, y={y}, data={data}')
if len(data) == 0:
return None
paintable = data[0].image.get_paintable()
drag_image = gtk.Image.new_from_paintable(paintable)
drag_image.set_opacity(0.5) # FIXME: not sure why transparency doesn't work
drag_source.set_icon(drag_image.get_paintable(), 128, 128) # FIXME: not sure why hot_x and hot_y don't work
content = gdk.ContentProvider.new_for_value(data)
return content
def on_dnd_begin(self, drag_source, data):
content = data.get_content()
print(f'in on_dnd_begin(); drag_source={drag_source}, data={data}, content={content}')
def on_dnd_end(self, drag, drag_data, flag):
print(f'in on_dnd_end(); drag={drag}, drag_data={drag_data}, flag={flag}')
完整代码可从 here 获得。我得到的输出:
in on_dnd_prepare(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, x=176.15234375, y=172.96092224121094, data=[<MediaFile 20210712_190722A.jpg>]
in on_dnd_begin(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, data=<__gi__.GdkWaylandDrag object at 0x7f75424f2e00 (GdkWaylandDrag at 0x41b6ac0)>, content=<__gi__.GdkContentProviderValue object at 0x7f75424eb040 (GdkContentProviderValue at 0x43a8c10)>
in on_dnd_accept(); drop=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, user_data=<__gi__.GdkWaylandDrop object at 0x7f7542374440 (GdkWaylandDrop at 0x7f7560157390)>
in on_dnd_enter(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gtype.c:4322: type id '0' is invalid
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: cant peek value table for type '<invalid>' which is not currently referenced
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gvalue.c:185: cannot initialize GValue with type '(null)', this type has no GTypeValueTable implementation
return Gio.Application.run(self, *args, **kwargs)
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): GLib-GIO-CRITICAL **: 22:21:40.071: g_task_return_error: assertion 'error != NULL' failed
in on_dnd_leave(); user_data=<Gtk.DropTarget object at 0x7f7542374480 (GtkDropTarget at 0x3f56aa0)>
我找不到任何关于如何为 DropTarget
适当设置 gdk
类型以及如何使 DragSource
和 DropTarget
通过 [= 交换数据的文档22=]。任何人都可以提供任何见解吗?提前致谢!
你实际上应该 return GObject.Value 在 ::prepare
例如,这里更改为使用 gliststore 将多个项目存储在单个 GObject 中,并从中创建一个 GValue:
def on_dnd_prepare(self, drag_source, x, y):
data = gio.ListStore()
data.splice(0, 0, self.get_selected_children())
print(data.get_n_items())
print(f'in on_dnd_prepare(); drag_source={drag_source}, x={x}, y={y}, data={data}')
if len(data) == 0:
return None
paintable = data[0].image.get_paintable() # TODO: make this nicer for multiple selections
drag_image = gtk.Image.new_from_paintable(paintable)
drag_image.set_opacity(0.5) # FIXME: not sure why transparency doesn't work
drag_source.set_icon(drag_image.get_paintable(), 128, 128) # FIXME: not sure why hot_x and hot_y don't work
content = gdk.ContentProvider.new_for_value(gobject.Value(gio.ListModel, data))
return content
您应该让放置目标接受正确的类型(GdkFileList 在 PyGObject 中损坏 https://gitlab.gnome.org/GNOME/pygobject/-/issues/468)
dnd = gtk.DropTarget.new(gio.ListModel, gdk.DragAction.COPY)
并且你应该修正 drop 函数的参数
def on_dnd_drop(self, drop_target, value, x, y):
print(f'in on_dnd_drop(); value={value}, x={x}, y={y}')
print(list(value))
您可以在此处访问创建 gobject.Value 时传递的内容作为值参数(在本例中为 MediaFiles 列表)