FLTK 1.3:如何确定fl_line()的绘图上下文?
问题:FLTK中fl_line()绘图上下文的控制方法
我正在为Linux寻找轻量C++ GUI框架,因此尝试在Ubuntu上熟悉FLTK 1.3。编写了可运行的代码,以30FPS绘制随机颜色的类信号波形动画,但存在问题:完全无法预测波形绘制位置——主窗口、按钮或弹出对话框上。
我似乎不理解如何为fl_line()这类绘图函数定义「上下文」。每次调用fl_line()时,线条都会绘制在不可预测的位置:聚焦按钮时线条画在按钮上;弹出对话框时,线条覆盖对话框及其按钮;关闭对话框后,线条仅覆盖「Close」按钮。
我的问题是:如何预测fl_line()的绘图「视觉上下文」(FLTK中是否有此类术语)?
原代码
#include <FL/Fl.H> #include <FL/Fl_Button.H> #include <FL/fl_message.H> #include <FL/Fl_Window.H> #include <FL/Fl_Double_Window.H> #include <FL/fl_draw.H> #include <cstdlib> namespace simulator { class Graph : public Fl_Widget { unsigned char color_[3] = {0, 0, 0}; public: Graph(int X, int Y, int W, int H) : Fl_Widget(X, Y, W, H) { } void draw(void) { this->plot(); } void plot() { // Pick some trash color in a trash way. color_[0] = rand() % 256; color_[1] = rand() % 256; color_[2] = rand() % 256; fl_color(color_[0], color_[1], color_[2]); // Draw some random "audio wave"-like thing // Dont care about cleaning the scene before drawing. int old[2] = {0, 0}; for (int i = 0; i < 400; ++i) { auto x = i * 2; auto y = rand() % 400; fl_line(old[0], old[1], x, y); old[0] = x; old[1] = y; } } }; class Main_Window : public Fl_Window { //public Fl_Double_Window { Fl_Button button_ {10, 10, 75, 25, "Close"}; Graph graph_; public: Main_Window() //: Fl_Double_Window(200, 100, 1200, 800, "Simulator") : Fl_Window(200, 100, 1200, 800, "Simulator") , graph_(10, 100, 500, 500) { begin(); Fl::visual(FL_RGB); button_.align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_CLIP | FL_ALIGN_WRAP); // Button will invoke dialog "Want to exit?" with "yes/no" buttons. button_.callback([](Fl_Widget* sender, void* window) { printf("callback called, sender %p, window %p\n", sender, window); reinterpret_cast<Main_Window*>(window)->hide(); }, this); // Maybe this is some illegal/unneeded/not-perfect widget adding code, and maybe widget adding is not needed at all; Lets forget about this for now. add(&button_); add(&graph_); end(); show(); graph_.show(); } void hide() override { fl_message_hotspot(false); fl_message_title("Close window"); if (fl_choice("Are you sure you want exit?", "No", "Yes", nullptr) == 1) { Fl_Window::hide(); } } void plot() { graph_.plot(); } }; } // namespace // 30 FPS wave animation. void winUpdate(void *_data) { auto *window = reinterpret_cast<simulator::Main_Window*>(_data); Fl::add_timeout(1.0 / 30.0, winUpdate, window); window->plot(); } int main(int argc, char *argv[]) { simulator::Main_Window window; window.show(argc, argv); Fl::add_timeout(1.0 / 30.0, winUpdate, &window); return Fl::run(); }
编译命令
/usr/bin/x86_64-linux-gnu-g++-12 test.cpp -o test -I/usr/include/c++/12 -std=c++20 -Wuninitialized -Wall -Wextra -Werror -Wno-deprecated-declarations -Wno-sign-compare -Wno-type-limits -Wunused-function -ggdb -fno-strict-aliasing -fwrapv -fno-stack-protector -fno-strict-overflow -Wfatal-errors `fltk-config --ldflags` -lfltk -lfltk_images -ljpeg -lstdc++ -lXfixes -lXext
解决方案
FLTK里没有专门的「视觉上下文」术语,但绘图操作始终绑定在当前激活的绘图表面上——这个表面通常是FLTK内部调度重绘时的目标Widget。你直接在定时器里调用graph_.plot()的问题在于:此时FLTK的绘图上下文可能不是Graph控件,而是任何正在处理重绘的控件(比如按钮、对话框),导致线条画到错误的地方。
核心修复步骤
- 让FLTK管理绘图时机:不要直接调用
plot(),而是调用graph_.redraw()。这会告诉FLTK需要重绘Graph控件,FLTK会在合适的时机自动切换到Graph的绘图上下文,调用其draw()方法。 - 限制绘图区域:在Graph的
draw()方法中,先设置裁剪区域,确保所有绘图操作只在控件自身范围内进行;同时绘制背景清理旧内容。 - 使用局部坐标:FLTK的绘图函数默认使用窗口全局坐标,需要转换为Graph控件的局部坐标(加上控件的x、y偏移),或者用裁剪区域自动约束。
修改后的关键代码
修改Graph类的draw和plot方法
void draw(void) { // 先绘制背景,清理之前的内容 fl_color(FL_WHITE); fl_rectf(x(), y(), w(), h()); // 设置裁剪区域,确保绘图只在控件范围内 fl_push_clip(x(), y(), w(), h()); this->plot(); fl_pop_clip(); } void plot() { color_[0] = rand() % 256; color_[1] = rand() % 256; color_[2] = rand() % 256; fl_color(color_[0], color_[1], color_[2]); int old[2] = {x(), y() + rand() % h()}; // 起始点使用控件局部坐标 for (int i = 1; i < w(); i += 2) { auto curr_x = x() + i; auto curr_y = y() + rand() % h(); fl_line(old[0], old[1], curr_x, curr_y); old[0] = curr_x; old[1] = curr_y; } }
修改定时器函数winUpdate
void winUpdate(void *_data) { auto *window = reinterpret_cast<simulator::Main_Window*>(_data); Fl::add_timeout(1.0 / 30.0, winUpdate, window); window->graph_.redraw(); // 替换为redraw,让FLTK调度绘图 }
修复说明
redraw()会标记Graph控件为需要重绘,FLTK的事件循环会在合适的时机调用其draw()方法,此时绘图上下文自动切换到Graph的表面,所有fl_line()操作都会正确绘制在Graph控件内。fl_push_clip()设置了绘图的边界,即使坐标超出控件范围,也不会绘制到其他控件上。- 绘图坐标加上了
x()和y()偏移,确保波形在Graph控件的局部区域内生成,不会跑到窗口的左上角(全局坐标原点)。
内容的提问来源于stack exchange,提问作者pavelkolodin
相关产品推荐
相关产品推荐

