列表中的拖放操作,再次探讨

我之前关于列表中的拖放操作的文章为了展示最简单、功能齐全的实现而做了一些妥协。其中之一就是我们只能在整个行周围绘制放置目标高亮,而实际上放置操作是将拖放的行插入到行之间。 让我们尝试做得更好!

更改目标

在简化版本中,我们使每一行都成为放置目标。这一次,我们将更改此设置,仅让列表本身接受放置操作。

gtk_drag_dest_set (list,
                   GTK_DEST_DEFAULT_MOTION|GTK_DEST_DEFAULT_DROP, 
                   entries, 1,
                    GDK_ACTION_MOVE);
g_signal_connect (list, "drag-data-received",
                  G_CALLBACK (drag_data_received), NULL);

如果你将此与之前完成的操作进行比较,你可能会注意到另一个变化:我们现在只请求移动和放置的默认行为,而不是 GTK_DEST_DEFAULT_ALL。我们不再请求高亮的默认行为,因为我们想自己处理它。

为了做到这一点,我们需要连接到 drag-motion 和 drag-leave 信号。这些信号会在放置目标(即列表)上发出。

 g_signal_connect (list, "drag-motion",
                   G_CALLBACK (drag_motion), NULL);
 g_signal_connect (list, "drag-leave",
                   G_CALLBACK (drag_leave), NULL);

注意间隙

我们改进拖动高亮的基本思路是,跟踪放置操作将发生的两个相邻行,并使用 GTK+ CSS 机制来创建合适的间隙高亮。

这需要太多代码才能在此处完整展示,但思路如下:使用 gtk_list_box_get_row_at_y() 查找当前在光标下的行。查看其分配以确定光标是在该行的上半部分还是下半部分。根据这一点,我们选择紧随其后的行或前一行作为我们行对的另一个成员。

row = gtk_list_box_get_row_at_y (list, y);
gtk_widget_get_allocation (row, &alloc);
if (y < alloc.y + alloc.height/2)
  {
    row_after = row;
    row_before = get_row_before (list, row);
  }
else
  {
    row_before = row;
    row_after = get_row_after (list, row);
  }

这里省略了一些特殊情况,例如,当悬停的行是第一行或最后一行,或者当我们悬停在列表中的“空白区域”上时。

CSS 高亮

我说过我们将使用 CSS 来创建高亮。最简单的方法是将样式类添加到我们找到的两行中

gtk_style_context_add_class (
                      gtk_widget_get_style_context (row_before),
                      "drag-hover-bottom");
gtk_style_context_add_class (
                      gtk_widget_get_style_context (row_after),
                      "drag-hover-top");

然后我们将使用一些自定义 CSS 来创建我们的高亮。如果你想知道:#4e9a06 是 Adwaita 主题中使用的拖动高亮颜色。

.row.drag-hover-top {
  border-top: 1px solid #4e9a06; 
}
.row.drag-hover-bottom {
  border-bottom: 1px solid #4e9a06; 
}

这再次省略了我之前提到的特殊情况。

我们是如何走到这一步的?

将行放置到它来自的位置不会有任何作用。以某种方式标记拖动开始的位置,使其变得显而易见是有意义的。我们可以再次使用 CSS 来实现这一点,并在我们的 drag_begin() 方法中为拖动的行添加一个样式类

gtk_style_context_add_class (gtk_widget_get_style_context (row),
                             "drag-row");

为了在我们悬停在其上方时给行添加一些高亮,我们在我们的 drag_motion() 处理程序中为其添加一个额外的样式类

if (row == drag_row)
  {
    gtk_style_context_add_class (gtk_widget_get_style_context (row),
                                 "drag-hover");
    row_before = get_row_before (row);
    row_after = get_row_after (row);
  }
else …

这是这些类的 CSS

.row.drag-row {
  color: gray;
  background: alpha(gray,0.2);
 }
 .row.drag-row.drag-hover {
  border-top: 1px solid #4e9a06;
  border-bottom: 1px solid #4e9a06;
  color: #4e9a06;
}

将所有内容放在一起

(抱歉光标坏了。我们应该在 gnome-shell 的屏幕录像机中修复这个问题。)

参考

  1. 完整的示例,testlist3.c
  2. GTK+ DND 文档

关于“列表中的拖放操作,再次探讨”的 2 条评论

  1. 太棒了!

    你是否看到一个简单的方法来实现拖放中的滚动,以及约束列表容器中拖动的行(而不是能够将它拖出窗口)?

    我有一个设计上的疑问是如何实现可访问和可发现的导航和重新排序。我不确定仅仅使用键盘快捷键是否是可行的方法。

    感谢这些帖子。当工具包开发人员从应用程序开发人员的角度看待工具包时,这非常清楚。

  2. 对滚动的支持最好需要工具包的一些支持。我想看看它。也许未来的帖子会涉及。

    至于可访问的重新排序——拖放实际上不涉及这里。你可以实现一个“重新排序模式”,在该模式中,活动行被高亮显示,并且可以使用上/下箭头键或一些覆盖的按钮来移动。

评论已关闭。