Использование графического интерфейса библиотеки FLTK при создании программ на языке программирования С++. Учебно-методическое пособие
.pdf
Рисование с помощью FLTK, как правило, производится в виртуальном методе draw(). Для этого необходимо реализовать свою версию метода draw(), которая заменяет исходный. После вызова функция draw() отрисовывает на экране шаги пользователя. Для выполнения поэтапного рисования и анимации лучше рисовать во внеэкранном буфере, который копируется на экран при каждом вызове метода draw(). Так как FLTK не сохраняет содержимое экрана, то оно будет удалено, когда окно будет свернуто и снова показано на экране. Содержимое в закадровом буфере будет сохранено. В следующем примере выполняется поэтапное рисование, а также показано использование такого закадрового буфера. При нажатии мыши и ее перемещении будет нарисована линия из точек, рисунок 13.
Рисунок 13 – Рисование
#include <Fl/Fl.H>
#include <FL/Fl_Double_Window.H> #include <Fl/Fl_Box.H>
#include <Fl/fl_draw.H> #define window_size 400
static Fl_Double_Window *main_window = 0; // основное окно static Fl_Offscreen offscreen_buffer = 0; // внеэкранный буфер /****************************************************/ /*Класс, копирующий во внеэкранный буфер*/
class canvas : public Fl_Box { void draw();
int handle(int event); public:
canvas(int x, int y, int w, int h);
};
31
canvas::canvas(int x, int y, int w, int h) : Fl_Box(x,y,w,h){} void canvas::draw() {
if(offscreen_buffer) {
//если внеэкранный буфер существует,
//то он выводится на экран
fl_copy_offscreen(x(), y(), w(), h(), offscreen_buffer, 0,0);
}
else {
main_window->make_current(); offscreen_buffer = fl_create_offscreen(w(), h()); if(!offscreen_buffer)
{fprintf(stderr,"Ошибка создания буфера"); exit(1);} fl_begin_offscreen(offscreen_buffer); // Рисуем в буфере fl_color(FL_WHITE);
fl_rectf(0, 0, w(), h());
fl_end_offscreen(); // Останавливаем рисование в буфере fl_copy_offscreen(x(), y(), w(), h(), offscreen_buffer, 0,0);
}
}
// Обработка событий
int canvas::handle(int event) { static char labeltext[100]; int button,x,y;
int retvalue = 0; static int x_old,y_old; static int push1st=0;
if (!offscreen_buffer) return 1; retvalue = Fl_Box::handle(event); switch (event) {
case FL_PUSH: case FL_DRAG:
button = Fl::event_button();
x= Fl::event_x();
y= Fl::event_y();
};
switch ( button ) {
case 1: // Левая кнопка мыши
sprintf(labeltext,"button= Left | Координата %d,%d",x,y); window()->label(labeltext);
retvalue = 1; break;
32
case 3: // Правая кнопка мыши
sprintf(labeltext," button= Right | Координата %d,%d",x,y); window()->label(labeltext);
retvalue = 1; break;
}
switch(event) {
case FL_PUSH:
if (push1st == 0) { x_old = x; y_old = y; push1st = 1; break;
} else {
push1st = 0; fl_begin_offscreen(offscreen_buffer); if (button==1) {
fl_color(FL_RED); fl_line(x_old,y_old,x,y);
} else { fl_draw_box(FL_BORDER_FRAME,x_old,y_old,(x-
x_old),(y-y_old),FL_BLUE);
}
fl_end_offscreen(); redraw();
}
case FL_DRAG:
{
push1st=0; fl_begin_offscreen(offscreen_buffer); fl_color(FL_BLACK);
fl_point(x,y); fl_end_offscreen(); redraw();
}
break;
default:
redraw();
break;
}
return retvalue;
}
33
int main (int argc, char **argv) {
main_window = new Fl_Double_Window(window_size, window_size, "Рисование с помощью мыши");
main_window->begin();
static canvas *os_box = new canvas(5,5,(window_size-10),(window_size-10)); main_window->end();
main_window->resizable(os_box); main_window->show(argc, argv); return Fl::run();
}
Как правило, двойные нажатия на клавишу мыши достаточно легко обнаружить, для этого достаточно получить значение функции Fl::event_clicks() и обработать реакцию на FL_PUSH. При двойном нажатии будет возвращено значение 0 для первого щелчка и 1 – для второго.
Если необходимо обработать реакцию на нажатие мыши, представленное в виде сложной последовательности, например, сначала идет одинарное нажатие, а затем двойное, в таком случае можно использовать таймер и счетчик нажатий. Пример фрагмента кода обработки нажатия мыши с таймером:
//обработка очереди событий int handle(int e) {
switch (e) {
case FL_PUSH: timer_extend(Fl::event_clicks());
break;
}
return(Fl_Window::handle(e));
}
//Установка таймера для разграничения отдельного
//и двойного нажатия мыши static void timer_extend(int cc) {
if (cc) Fl::remove_timeout(wait_click); Fl::add_timeout(0.25, wait_click, (void*)cc);
}
//Функция обратного вызова для таймера static void wait_click(void *v) {
intptr_t cc = (intptr_t)v;
int ic = (int)(cc & 0x0F) + 1;
printf("Количество нажатий: %d\n", ic); // Выводит 1 или 2
}
34
Для анимации в виде вывода последовательности изображений формата JPEG используется загрузка всех используемых изображений в массив формата Fl_JPEG_Image, например, для массива, хранящего три изображения:
Fl_JPEG_Image *jpgs[3];
А также их отображение с необходимой задержкой с помощью функций задания таймера Fl::add_timeout(0.1, ShowNextImage_CB) и его реализации Fl::repeat_timeout(), где ShowNextImage_CB() – функция обратного вызова для отображения изображений.
В FLTK с версии 1.3.0 используется древовидный виджет, позволяющий отображать структуру в древовидном стиле, рисунок 14.
Рисунок 14 – Древовидное отображение структуры
Пример отображения структуры с помощью виджета «Дерево» взят из примера библиотеки FLTK. В примере с помощью формата XPM определяются два значка, они будут добавлены в сочленения дерева в зависимости от того, является ли объект папкой или файлом.
Если необходимо, кроме текстового представления элементов структуры, добавить различные нажимаемые графические элементы (иконки) без встраивания виджета в элемент, то можно использовать подход с обратным вызовом для дерева при каждом нажатии, например, для экземпляра дерева
tree ->callback(MyTree_CB);
// определяем условия обратного вызова tree->when(FL_WHEN_CHANGED|FL_WHEN_RELEASE);
// обратный вызов работает даже при повторном выборе tree->item_reselect_mode(FL_TREE_SELECTABLE_ALWAYS);
В обратном вызове получаем элемент, на который нажали, с помощью функции tree->callback_item(), и причину обратного вызова с помощью tree- >callback_reason() (например, selected, reselected, deselected). Используя элемент,
возвращенный функцией callback_item(), вызываем свой метод типа
Fl_Tree_Item, например, MyTreeItem::my_handle() для обработки события нажатия на нем. Также необходимо определять координаты, где произошло нажатие мыши, например, с помощью функций Fl::event_x() и Fl::event_y() и произошел ли он над одним из нарисованных графических элементов. Если нажатие мышью произошло в нужном месте, то необходимо перерисовать графическое изображение. Фрагмент кода древовидной структуры с
35
нажимаемыми графическими элементами демонстрирует выбранный подход, рисунок 15:
class MyTreeItem : public Fl_Tree_Item { Fl_Pixmap *xpm1; int xp1[4]; Fl_Pixmap *xpm2; int xp2[4];
int event_inside(int v[4]) {
return Fl::event_inside(v[0],v[1],v[2],v[3]);
}
public:
MyTreeItem(Fl_Tree *tree, const char *name) : Fl_Tree_Item(tree) { label(name);
labelsize(14);
// Инициализация графических XPM элементов xpm1 = &eye_open_pixmap;
xpm2 = &eye_open_pixmap;
}
// Ручная отрисовка элемента int draw_item_content(int render) {
int X = label_x(), Y = label_y(),
W= label_w(), H = label_h(); // Render background + label if ( render ) {
fl_color(FL_WHITE); fl_rectf(X,Y,W,H); fl_color(FL_BLACK);
if ( label() ) fl_draw(label(), X,Y,W,H, FL_ALIGN_LEFT);
}
int lw=0, lh=0; if ( label() ) {
lw=0; lh=0; fl_measure(label(), lw, lh);
}
X+= lw + 8;
//Вычисление размера области рисования int yoff = (H – xpm1->h()) / 2;
xp1[0] = X; xp1[1] = Y+yoff; xp1[2] = xpm1->w(); xp1[3] = xpm1->h();
X = label_x()+label_w()-xpm2->w()-10;
xp2[0] = X; xp2[1] = Y+yoff; xp2[2] = xpm2->w(); xp2[3] = xpm2->h();
X += xpm2->w();
//Рисование изображений
if ( render ) {
36
xpm1->draw(xp1[0], xp1[1], xp1[2], xp1[3]); xpm2->draw(xp2[0], xp2[1], xp2[2], xp2[3]);
}
return X;
}
// Обработка событий void my_handle() {
printf("Нажато: %s\n", label());
// Изменяем вид графического элемента
if ( event_inside(xp1) ) xpm1 = (xpm1 == &eye_open_pixmap) ? &eye_close_pixmap : &eye_open_pixmap;
if ( event_inside(xp2) ) xpm2 = (xpm2 == &eye_open_pixmap) ? &eye_close_pixmap : &eye_open_pixmap;
}
};
// Возвращаем источник нажатия
static const char* reason_str(Fl_Tree_Reason reason) { switch (reason) {
case FL_TREE_REASON_NONE: return "none";
case FL_TREE_REASON_SELECTED: return "selected"; case FL_TREE_REASON_DESELECTED: return "deselected"; case FL_TREE_REASON_RESELECTED: return "reselected"; case FL_TREE_REASON_OPENED: return "opened";
case FL_TREE_REASON_CLOSED: return "closed"; case FL_TREE_REASON_DRAGGED: return "dragged";
}
return "???";
}
static void MyTree_CB(Fl_Widget *w, void *userdata) { char itempath[256];
Fl_Tree *tree = (Fl_Tree*)w;
Fl_Tree_Item *item = tree->callback_item(); if ( !item ) return;
if ( tree->item_pathname(itempath, sizeof(itempath)-1, item) != 0 ) return; printf("callback: itempath='%s' reason=%s event_x,y=%d,%d\n",
itempath, reason_str(tree->callback_reason()), Fl::event_x(), Fl::event_y()); // Проверяем элемент
if ( strstr(itempath, "MyItem") ) { MyTreeItem *myitem = (MyTreeItem*)item; switch ( tree->callback_reason() ) {
37
case FL_TREE_REASON_SELECTED: case FL_TREE_REASON_RESELECTED:
myitem->my_handle(); break;
default:
break;
}
}
}
Рисунок 15 – Древовидное отображение структуры с нажимаемыми изображениями
Одним из необычных реализаций класса, использующегося для группирования виджетов, является класс Wizard, рисунок 16.
Рисунок 16 – Схема наследования класса Wizard
Данный класс отображает вкладки и их изменяет. Его основная цель – поддержка «мастеров», которые помогают пользователю выполнять задачи настройки или устранения неполадок. В классе Wizard используются только методы для перехода на следующую или предыдущую вкладку.
Фрагмент отображения вкладки класса Wizard и кнопки перехода, рисунок
17:
Fl_Group *g = new Fl_Group(0,0,400,300);
Fl_Button *next = new Fl_Button(290,265,100,25,"Next"); next->callback(next_cb);
38
Fl_Multiline_Output *out = new Fl_Multiline_Output(10,30,400-20,300-80,"Добро пожаловать");
out->labelsize(20); out->align(FL_ALIGN_TOP|FL_ALIGN_LEFT); out->value("Первая страница");
g->end();
Рисунок 17 – Вкладка класса Wizard и кнопка перехода
Если необходимо отобразить данные в табличном виде с дополнительным выбором данных из нескольких строк, рисунок 18, то для отображения можно использовать класс Fl_Table, а для реализации сложного выбора можно реализовать свой класс от Fl_Table_Row. Фрагмент кода задания своего класса для отображения таблицы демонстрирует применение данной функции:
class MyTable : public Fl_Table_Row { // Заголовок таблицы
void DrawHeader(const char *s, int X, int Y, int W, int H) { fl_push_clip(X,Y,W,H);
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, row_header_color()); fl_color(FL_BLACK);
fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER); fl_pop_clip();
}
// Заполнение таблицы в зависимости от вида наполнения
void draw_cell(TableContext context, int ROW=0, int COL=0, int X=0, int Y=0, int W=0, int H=0) {
static char s[40]; switch ( context ) {
case CONTEXT_STARTPAGE: // Действия до рисования fl_font(FL_HELVETICA, 16);
39
return;
case CONTEXT_COL_HEADER: // Заголовок таблицы sprintf(s,"%c",'A'+COL);
DrawHeader(s,X,Y,W,H);
return;
case CONTEXT_ROW_HEADER: // Строки таблицы sprintf(s,"%03d:",ROW); DrawHeader(s,X,Y,W,H);
return;
case CONTEXT_CELL: { // Ячейки таблицы sprintf(s,"%d",ROW*MAX_COLS+COL); int fgcol = FL_BLACK;
int bgcol = FL_WHITE; if ( row_selected(ROW) ) {
fgcol = FL_WHITE; bgcol = 0xaa4444;
}
fl_draw_box(FL_THIN_UP_BOX, X,Y,W,H, bgcol); fl_color(fgcol);
fl_draw(s, X,Y,W,H, FL_ALIGN_CENTER); return;
}
default:
return;
}
}
Рисунок 18 – Пример реализации класса Fl_Table
Способ отображения значков (элементов графического интерфейса, обозначающих приложение, файл, каталог, окно, компонент операционной
40
