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