Drag and Drop#

See also

Drag-and-Drop in the GTK documentation.

GTK drag-and-drop works with drag sources and drop targets. These are event controllers that can be set to any widget using Gtk.Widget.add_controller(). The data begin moved in the operation is provided through a Gdk.ContentProvider.

Drag sources#

Gtk.DragSource is the event controller that allows a widget to be used as a drag source. You can set up everything needed for the drag-and-drop operation ahead of time or do it on the fly using the signals provided by Gtk.DragSource.

You can use Gtk.DragSource.set_content() to set the Gdk.ContentProvider that will be sent to drop targets. A content provider is usually created using Gdk.ContentProvider.new_for_value() where you only pass the value to send. To pass different values for multiple possible targets you can use Gdk.ContentProvider.new_union() were you can pass a list of Gdk.ContentProviders.

Gtk.DragSource provides signals for the different stages of the drag event. The prepare signal is emitted when a drag is about to be initiated, here you should return the Gdk.ContentProvider that will be sent, otherwise the one set with Gtk.DragSource.set_content() will be used. The drag-begin signal is emitted when the drag is started, here you can do things like changing the icon attached to the cursor when dragging using Gtk.DragSource.set_icon(). Also the drag-end signal is provided, you can use it to undo things done in the previous signals. Finally drag-cancel allows you to do things when the operation has been cancelled.

Drop targets#

Gtk.DropTarget is the event controller to receive drag-and-drop operations in a widget. When creating a new drop target with Gtk.DropTarget.new() you should provide the data type and Gdk.DragAction that your target accepts. If you want to support multiple data types you can pass GObject.TYPE_NONE and then use Gtk.DropTarget.set_gtypes() where you can pass a list of types, be aware that the order of the list establish the priorities.

Gtk.DropTarget provides multiple signals for the process. These are accept, drop, enter, leave, and motion. Generally connecting to drop is only needed, this signal will receive the value sended by the Gtk.DragSource.

For more complex use cases checkout Gtk.DropTargetAsync.

Example#

../../_images/drag_and_drop.png
  1import gi
  2
  3gi.require_version("Gdk", "4.0")
  4gi.require_version("Gtk", "4.0")
  5from gi.repository import Gdk, GObject, Gtk
  6
  7
  8class DragDropWindow(Gtk.ApplicationWindow):
  9    def __init__(self, *args, **kargs):
 10        super().__init__(*args, **kargs, title="Drag and Drop Example")
 11
 12        self.set_default_size(500, 400)
 13
 14        views_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True)
 15        self.set_child(views_box)
 16
 17        flow_box = Gtk.FlowBox(selection_mode=Gtk.SelectionMode.NONE)
 18        views_box.append(flow_box)
 19        flow_box.append(SourceFlowBoxChild("Item 1", "image-missing"))
 20        flow_box.append(SourceFlowBoxChild("Item 2", "help-about"))
 21        flow_box.append(SourceFlowBoxChild("Item 3", "edit-copy"))
 22
 23        views_box.append(Gtk.Separator())
 24
 25        self.target_view = TargetView(vexpand=True)
 26        views_box.append(self.target_view)
 27
 28
 29class SourceFlowBoxChild(Gtk.FlowBoxChild):
 30    def __init__(self, name, icon_name):
 31        super().__init__()
 32
 33        self.name = name
 34        self.icon_name = icon_name
 35
 36        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
 37        self.set_child(box)
 38
 39        icon = Gtk.Image(icon_name=self.icon_name)
 40        label = Gtk.Label(label=self.name)
 41
 42        box.append(icon)
 43        box.append(label)
 44
 45        drag_controller = Gtk.DragSource()
 46        drag_controller.connect("prepare", self.on_drag_prepare)
 47        drag_controller.connect("drag-begin", self.on_drag_begin)
 48        self.add_controller(drag_controller)
 49
 50    def on_drag_prepare(self, _ctrl, _x, _y):
 51        item = Gdk.ContentProvider.new_for_value(self)
 52        string = Gdk.ContentProvider.new_for_value(self.name)
 53        return Gdk.ContentProvider.new_union([item, string])
 54
 55    def on_drag_begin(self, ctrl, _drag):
 56        icon = Gtk.WidgetPaintable.new(self)
 57        ctrl.set_icon(icon, 0, 0)
 58
 59
 60class TargetView(Gtk.Box):
 61    def __init__(self, **kargs):
 62        super().__init__(**kargs)
 63
 64        self.stack = Gtk.Stack(hexpand=True)
 65        self.append(self.stack)
 66
 67        empty_label = Gtk.Label(label="Drag some item, text, or files here.")
 68        self.stack.add_named(empty_label, "empty")
 69        self.stack.set_visible_child_name("empty")
 70
 71        box = Gtk.Box(
 72            orientation=Gtk.Orientation.VERTICAL,
 73            vexpand=True,
 74            valign=Gtk.Align.CENTER,
 75        )
 76        self.stack.add_named(box, "item")
 77
 78        self.icon = Gtk.Image()
 79        box.append(self.icon)
 80        self.label = Gtk.Label()
 81        box.append(self.label)
 82
 83        self.text = Gtk.Label()
 84        self.stack.add_named(self.text, "other")
 85
 86        drop_controller = Gtk.DropTarget.new(
 87            type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY
 88        )
 89        drop_controller.set_gtypes([SourceFlowBoxChild, Gdk.FileList, str])
 90        drop_controller.connect("drop", self.on_drop)
 91        self.add_controller(drop_controller)
 92
 93    def on_drop(self, _ctrl, value, _x, _y):
 94        if isinstance(value, SourceFlowBoxChild):
 95            self.label.props.label = value.name
 96            self.icon.props.icon_name = value.icon_name
 97            self.stack.set_visible_child_name("item")
 98
 99        elif isinstance(value, Gdk.FileList):
100            files = value.get_files()
101            names = ""
102            for file in files:
103                names += f"Loaded file {file.get_basename()}\n"
104            self.text.props.label = names
105            self.stack.set_visible_child_name("other")
106
107        elif isinstance(value, str):
108            self.text.props.label = value
109            self.stack.set_visible_child_name("other")
110
111
112def on_activate(app):
113    win = DragDropWindow(application=app)
114    win.present()
115
116
117app = Gtk.Application(application_id="com.example.App")
118app.connect("activate", on_activate)
119
120app.run(None)