Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Использование графического интерфейса библиотеки FLTK при создании программ на языке программирования С++. Учебно-методическое пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
777 Кб
Скачать

Рисование с помощью 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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]