Drag and Drop - PyGObject

Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Edit this page
Toggle table of contents sidebar

# 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

      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([SourceFlowBoxChild, Gdk.FileList, str])
     92        drop_controller.connect('drop', self.on_drop)
     93        self.add_controller(drop_controller)
     94
     95    def on_drop(self, _ctrl, value, _x, _y):
     96        if isinstance(value, SourceFlowBoxChild):
     97            self.label.props.label = value.name
     98            self.icon.props.icon_name = value.icon_name
     99            self.stack.set_visible_child_name('item')
    100
    101        elif isinstance(value, Gdk.FileList):
    102            files = value.get_files()
    103            names = ''
    104            for file in files:
    105                names += f'Loaded file {file.get_basename()}\n'
    106            self.text.props.label = names
    107            self.stack.set_visible_child_name('other')
    108
    109        elif isinstance(value, str):
    110            self.text.props.label = value
    111            self.stack.set_visible_child_name('other')
    112
    113
    114def on_activate(app):
    115    win = DragDropWindow(application=app)
    116    win.present()
    117
    118
    119app = Gtk.Application(application_id='com.example.App')
    120app.connect('activate', on_activate)
    121
    122app.run(None)