【问题标题】:Scrollable drawing in Gtk::LayoutGtk::Layout 中的可滚动绘图
【发布时间】:2016-10-30 02:59:07
【问题描述】:

我想在Gtk::Layout 中使用自定义绘图。也就是说,我正在使用 Gtk3 (GTKmm 3.14.0) 的 C++ 绑定,并且我在自定义绘图顶部的“画布”上嵌入了小部件。基本上这工作得很好。

现在问题与滚动有关。 Gtk::Layout 可以放在Gtk::ScrolledWindow 中,当可滚动区域设置为大于可见分配时,将显示滚动条。不幸的是,这些滚动条只影响嵌入小部件的位置,而我的自定义绘图仍保持在窗口内的固定位置。

这意味着,Gtk::Allocation 和 cairo 上下文似乎都与可见区域相关,而不是与扩展的虚拟“画布”相关。我可以通过访问滚动条的调整来解决这个问题,然后相应地翻译 cairo 上下文......

我的问题是:

  • 这是处理此类可滚动绘图的正确方法吗?
  • 或者有什么方法可以让框架为我完成这项工作?

【问题讨论】:

  • 我理解正确吗:您是直接在Gtk::Layout 内绘图?我认为你不应该这样做。如果你想画一些东西,你应该使用developer.gnome.org/gtkmm/unstable/classGtk_1_1DrawingArea.html
  • 不应该?请阅读文档
  • 引用developer.gnome.org/gtkmm/unstable/… Gtk::Layout:包含子小部件和/或自定义绘图的无限可滚动区域。 Gtk::Layout 与 Gtk::DrawingArea 类似,因为它是“空白板”,默认情况下除了绘制空白背景之外什么都不做。不同之处在于它支持原生滚动(您可以直接将其添加到 Gtk::ScrolledWindow),并且它可以包含子小部件,因为它是 Gtk::Container。但是,如果您只是要绘图,则 Gtk::DrawingArea 是更好的选择,因为它的开销较低。
  • 对不起。这对我来说实际上是新闻。我只见过人们在绘制其他小部件时被告知“使用 GtkDrawinArea”。
  • 由于 GtkLayout 本身就知道如何滚动,我认为您唯一的选择是查询调整。不过,不是 100% 确定。

标签: scroll drawing gtk3 cairo gtkmm3


【解决方案1】:

gtk+3.0-3.14.5 的源代码(在Debian/Stable 中)来看,Gtk::Layout 没有调整绘图上下文。它只是调用从GtkWidget 继承的draw() 函数。另一方面,Gtk::Layout 是一个成熟的容器(它继承自 Gtk::Container),并且它是可滚动的,这意味着它通过将合适的分配(屏幕区域)传递给每个嵌入式子小部件——在这方面它确实处理与滚动虚拟画布相关的移动和剪辑(调用gdk_window_move_resize())。

因此,如果我们想将嵌入的子小部件与自定义绘图相结合,我们需要手动弥合这种差异。这实际上很简单:我们需要做的就是查看滚动条对应的Gtk::Adjusments。因为这些调整的正好是可见视口的左上角。现在,如果我们希望我们的自定义绘图使用绝对画布坐标,我们只需要translate() 给定的 Cairo 上下文。当心:重要的是save() 状态和restore() 完成后的原始状态,否则这些翻译会累积。


这里有一些示例代码来演示这个自定义绘图

  • 我们从Gtk::Layout派生了一个名为Canvas的自定义容器类
  • 我们覆盖了on_draw() 处理程序,因为只有对嵌入式子小部件的所有大小分配都已处理
  • 分层:子部件总是按照它们添加到Gtk::Layout 容器中的顺序绘制。任何在调用继承的on_draw() 函数之前 完成的自定义绘图都将在这些小部件的下方之后完成的任何绘图都将发生在它们之上。
  • 如果需要,我们可以使用foreach(callback)机制访问所有子widget,找出它们当前的位置和扩展名

    void
    Canvas::determineExtension()
    {
        if (not recalcExtension_) return;
    
        uint extH=20, extV=20;
        Gtk::Container::ForeachSlot callback
          = [&](Gtk::Widget& chld)
                  {
                    auto alloc = chld.get_allocation();
                    uint x = alloc.get_x();
                    uint y = alloc.get_y();
                    x += alloc.get_width();
                    y += alloc.get_height();
                    extH = max (extH, x);
                    extV = max (extV, y);
                  };
        foreach(callback);
        recalcExtension_ = false;
        set_size (extH, extV);  // define extension of the virtual canvas
    }
    
    
    bool
    Canvas::on_draw(Cairo::RefPtr<Cairo::Context> const& cox)
    {
      if (shallDraw_)
        {
          uint extH, extV;
          determineExtension();
          get_size (extH, extV);
    
          auto adjH = get_hadjustment();
          auto adjV = get_vadjustment();
          double offH = adjH->get_value();
          double offV = adjV->get_value();
    
          cox->save();
          cox->translate(-offH, -offV);
    
          // draw red diagonal line
          cox->set_source_rgb(0.8, 0.0, 0.0);
          cox->set_line_width (10.0);
          cox->move_to(0, 0);
          cox->line_to(extH, extV);
          cox->stroke();
          cox->restore();
    
          // cause child widgets to be redrawn
          bool event_is_handled = Gtk::Layout::on_draw(cox);
    
          // any drawing which follows happens on top of child widgets...
          cox->save();
          cox->translate(-offH, -offV);
    
          cox->set_source_rgb(0.2, 0.4, 0.9);
          cox->set_line_width (2.0);
          cox->rectangle(0,0, extH, extV);
          cox->stroke();
          cox->restore();
    
          return event_is_handled;
        }
      else
        return Gtk::Layout::on_draw(cox);
    }
    

【讨论】:

  • 好答案!我想知道是否可以在我的DrawingArea 中添加一个Overlay 以便在其上添加子小部件,但我刚刚了解到Layout 存在并且似乎正是为了这个目的。如果我第一次不能自己做对,我一定会参考这个。 :D
猜你喜欢
  • 2019-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多