在 gtkmm 中滚动到 TextView 的底部
Scrolling to the bottom of the TextView in gtkmm
布局如下:
有一个Gtk::ScrollWindow
,里面是Gtk::TextView
,后者是派生的class,叫做TextArea
。
作为测试,有一个按钮在一行中向 TextView 添加一些 textgt,并尝试立即滚动到底部。
代码:
void MainWindow::onButtonPress()
{
Glib::RefPtr<Gtk::TextBuffer::Tag> boldTag = Gtk::TextBuffer::Tag::create();
boldTag->property_weight()=800;
textarea->get_buffer()->get_tag_table()->add(boldTag);
textarea->get_buffer()->insert_with_tag(textarea->get_buffer()->end(), "\ntest text", boldTag);
auto iter = textarea->get_buffer()->end();
iter.set_line_offset(0);
textarea->scroll_to(iter);
}
有趣的是确实发生了滚动,但不是滚动到最后一行,而是滚动到倒数第二行。将 -1
添加为偏移量的愚蠢尝试会立即产生错误,因为该值必须是非负数。
我认为您的问题是您使用迭代器在要插入文本的缓冲区中移动。作为 documentation suggests:
Iterators are not valid indefinitely; whenever the buffer is modified
in a way that affects the number of characters in the buffer, all
outstanding iterators become invalid.
我建议不要使用迭代器,而是使用指向缓冲区末尾的 Gtk::TextBuffer::Mark
。与迭代器不同,标记代表:
A position in the buffer, preserved across buffer modifications.
Gtk::TextView
小部件也有处理标记的 scroll_to
方法的重载,其中之一是:
void Gtk::TextView::scroll_to(const Glib::RefPtr< TextBuffer::Mark >& mark,
double within_margin = 0
)
您可以在下面的简化示例中看到它的实际效果:
#include <iostream>
#include <string>
#include <gtkmm.h>
// Move mark to the right of newly added text (see below):
constexpr bool RIGHT_GRAVITY = false;
class MyWindow : public Gtk::ApplicationWindow
{
public:
MyWindow()
{
m_button.signal_clicked().connect([this](){OnButtonPressed();});
// Create a mark that "points" to the end of the buffer. This
// mark will be updated accordinly as the buffer is modified:
m_endMark = m_textArea.get_buffer()->create_mark(m_textArea.get_buffer()->end(), RIGHT_GRAVITY);
m_scrolledWindow.add(m_textArea);
m_layout.attach(m_scrolledWindow, 0, 0, 1, 1);
m_layout.attach(m_button, 0, 1, 1, 1);
add(m_layout);
}
void OnButtonPressed()
{
// Insert new line at the end of the Gtk::TextView:
static int lineNumber = 0;
m_textArea.get_buffer()->insert(m_textArea.get_buffer()->end(), "\n" + std::to_string(lineNumber) +" - test text");
++lineNumber;
// Scroll down to the mark:
m_textArea.scroll_to(m_endMark);
}
private:
Gtk::Grid m_layout;
Gtk::ScrolledWindow m_scrolledWindow;
Gtk::TextView m_textArea;
Glib::RefPtr<Gtk::TextBuffer::Mark> m_endMark;
Gtk::Button m_button{"Add line at the end..."};
};
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create(argc, argv, "so.question.q66329582");
MyWindow window;
window.show_all();
return app->run(window);
}
布局如下:
有一个Gtk::ScrollWindow
,里面是Gtk::TextView
,后者是派生的class,叫做TextArea
。
作为测试,有一个按钮在一行中向 TextView 添加一些 textgt,并尝试立即滚动到底部。
代码:
void MainWindow::onButtonPress()
{
Glib::RefPtr<Gtk::TextBuffer::Tag> boldTag = Gtk::TextBuffer::Tag::create();
boldTag->property_weight()=800;
textarea->get_buffer()->get_tag_table()->add(boldTag);
textarea->get_buffer()->insert_with_tag(textarea->get_buffer()->end(), "\ntest text", boldTag);
auto iter = textarea->get_buffer()->end();
iter.set_line_offset(0);
textarea->scroll_to(iter);
}
有趣的是确实发生了滚动,但不是滚动到最后一行,而是滚动到倒数第二行。将 -1
添加为偏移量的愚蠢尝试会立即产生错误,因为该值必须是非负数。
我认为您的问题是您使用迭代器在要插入文本的缓冲区中移动。作为 documentation suggests:
Iterators are not valid indefinitely; whenever the buffer is modified in a way that affects the number of characters in the buffer, all outstanding iterators become invalid.
我建议不要使用迭代器,而是使用指向缓冲区末尾的 Gtk::TextBuffer::Mark
。与迭代器不同,标记代表:
A position in the buffer, preserved across buffer modifications.
Gtk::TextView
小部件也有处理标记的 scroll_to
方法的重载,其中之一是:
void Gtk::TextView::scroll_to(const Glib::RefPtr< TextBuffer::Mark >& mark,
double within_margin = 0
)
您可以在下面的简化示例中看到它的实际效果:
#include <iostream>
#include <string>
#include <gtkmm.h>
// Move mark to the right of newly added text (see below):
constexpr bool RIGHT_GRAVITY = false;
class MyWindow : public Gtk::ApplicationWindow
{
public:
MyWindow()
{
m_button.signal_clicked().connect([this](){OnButtonPressed();});
// Create a mark that "points" to the end of the buffer. This
// mark will be updated accordinly as the buffer is modified:
m_endMark = m_textArea.get_buffer()->create_mark(m_textArea.get_buffer()->end(), RIGHT_GRAVITY);
m_scrolledWindow.add(m_textArea);
m_layout.attach(m_scrolledWindow, 0, 0, 1, 1);
m_layout.attach(m_button, 0, 1, 1, 1);
add(m_layout);
}
void OnButtonPressed()
{
// Insert new line at the end of the Gtk::TextView:
static int lineNumber = 0;
m_textArea.get_buffer()->insert(m_textArea.get_buffer()->end(), "\n" + std::to_string(lineNumber) +" - test text");
++lineNumber;
// Scroll down to the mark:
m_textArea.scroll_to(m_endMark);
}
private:
Gtk::Grid m_layout;
Gtk::ScrolledWindow m_scrolledWindow;
Gtk::TextView m_textArea;
Glib::RefPtr<Gtk::TextBuffer::Mark> m_endMark;
Gtk::Button m_button{"Add line at the end..."};
};
int main(int argc, char* argv[])
{
auto app = Gtk::Application::create(argc, argv, "so.question.q66329582");
MyWindow window;
window.show_all();
return app->run(window);
}