Инсталляция библиотеки FLTK
“Если код и комментарии противоречат друг другу,
то, вероятно, оба неверны”.
Норм Шрайер (Norm Schreyer)
В этом приложении показано, как загрузить, инсталлировать и отредактировать связи графической библиотеки FLTK.
Г.1. Введение
Мы выбрали библиотеку FLTK (Fast Light Tool Kit) (читается как “фултик”) как основу для нашего представления графики и решения задач, связанных с созданием графического пользовательского интерфейса, потому что она является переносимой, относительно простой, относительно широко распространенной и легко инсталлируемой. Мы покажем, как инсталлировать библиотеку FLTK в среде Microsoft Visual Studio, потому что именно это интересует большинство наших студентов и вызывает у них наибольшие затруднения. Если вы используете какую-то другую систему (как некоторые из наших студентов), просто поищите в главном каталоге загружаемых файлов (раздел Г.3) инструкции, касающиеся вашей системы.
Если вы используете библиотеку, не являющуюся частью стандарта ISO C++, то вам придется загрузить ее, инсталлировать и правильно использовать в своем коде. Эта задача редко бывает тривиальной, так что инсталлирование библиотеки FLTK — неплохая задача, потому что загрузка и инсталлирование даже самой хорошей библиотеки часто вызывают трудности, если вы никогда не делали этого раньше. Не стесняйтесь спрашивать совета у людей, делавших это раньше, но не перепоручайте им свои проблемы, а учитесь у них.
Отметим, что в реальности файлы и процедуры могут немного отличаться от того, что мы описываем. Например, может появиться новая версия библиотеки FLTK или вы можете изменить версию Visual Studio, или вообще перейти в совершенно другую среду.
Г.2. Загрузка библиотеки FLTK
Перед тем как делать что-нибудь, сначала проверьте, не установлена ли библиотека FLTK на вашем компьютере (см. раздел Г.5). Если нет, то загрузите файлы библиотеки.
1. Зайдите на веб-страницу http://fltk.org. (Если не получится, то можете скопировать эти файлы с веб-сайта, посвященного этой книге (www.stroustrup.com/Programming/FLTK).
2. Щелкните на кнопке Download в навигационном меню.
3. Выберите в выпадающем меню пункт FLTK 1.1.x и щелкните на кнопке Show Download Locations.
4. Выберите место, откуда вы будете загружать файл, и загрузите файл с расширением .zip.
Полученный вами файл записан в формате zip. Это формат архивации, удобный для передачи файлов по сети. Для того чтобы разархивировать файлы и привести их к обычному виду, вам нужна специальная программа, например, в системе Windows для этого подходят программы WinZip и 7-Zip.
Г.3. Инсталляция библиотеки FLTK
При выполнении инструкций может возникнуть одна из двух проблем: за время, прошедшее с момента выхода нашей книги, что-то изменилось (это случается), или вы не понимаете терминологию (в этом случае мы ничем не можем вам помочь; извините). В последнем случае позовите друга, который вам все объяснит.
1. Распакуйте загруженный файл и откройте основной каталог,
fltk-1.1.
? В каталоге системы C++ (например, vc2005
или vcnet
) откройте файл fltk.dsw
. Если вас попросят подтвердить обновление всех старых проектов, отвечайте Yes to All.2. В меню Build выберите команду Build Solution. Это может занять несколько минут. Исходный код компилируется в статические библиотеки (static link libraries), поэтому вам не придется компилировать исходный код библиотеки FLTK каждый раз при создании нового проекта. После завершения процесса закройте среду Visual Studio.
3. В основном каталоге библиотеки FLTK откройте подкаталог
lib
. Скопируйте (а не просто переместите или перетащите) все файлы с расширением .lib
, за исключением файла README.lib
(их должно быть семь) в каталог C:\Program Files\Microsoft Visual Studio\Vc\lib.4. Вернитесь в основной каталог библиотеки FLTK и скопируйте подкаталог FL в каталог C:\Program Files\Microsoft Visual Studio\Vc\include.
Эксперты скажут вам, что было бы лучше инсталлировать библиотеку, а не копировать файлы в каталоги C:\Program Files\Microsoft Visual Studio\Vc\lib и C:\Program Files\Microsoft Visual Studio\Vc\include. Они правы, но мы не стремимся быть экспертами по среде Visual Studio. Если эксперты будут настаивать, попросите их продемонстрировать лучшую альтернативу.
Г.4. Использование библиотеки FLTK в среде Visual Studio
1. Создайте новый проект в среде Visual Studio, внеся одно изменение в обычной процедуре: выбирая тип проекта, выберите опцию “Win32 project”, а не “Console application”. Убедитесь, что вы создаете “Empty project”; в противном случае мастер добавит в ваш проект много лишнего кода, который вы не поймете и вряд ли будете использовать.
2. Находясь в среде Visual Studio, выберите команду Project в главном меню, а в выпадающем меню выполните команду Properties.
3. В левом меню окна Properties щелкните на пиктограмме Linker. В открывающемся подменю выберите команду Input. В поле редактирования Dependencies, находящемся справа, введите следующий текст:
fltkd.lib wsock32.lib comctl32.lib fltkjpegd.lib fltkimagesd.lib
(Следующий шаг может оказаться ненужным, поскольку в настоящее время он выполняется по умолчанию.)
В поле редактирования Ignore Specific Library введите следующий текст:
libcd.lib
4. Этот шаг может оказаться ненужным, так как в настоящее время опция /MDd включается по умолчанию. В левом меню того же самого окна Properties выберите команду C/C++, чтобы открыть другое подменю. Открыв подменю, выберите команду Code Generation. В правом меню измените опцию Runtime Library на Multi-threaded Debug DLL (/MDd). Щелкните на кнопке OK, чтобы закрыть окно Properties.
Г.5. Как тестировать, если не все работает
Создайте новый файл с расширением
.cpp
в новом проекте и введите следующий код. Он должен скомпилироваться без проблем.
#include
#include
#include
int main()
{
Fl_Window window(200, 200, "Window title");
Fl_Box box(0,0,200,200,"Hey, I mean, Hello, World!");
window.show();
return Fl::run();
}
Если что-то не работает, выполните следующее.
• Если вы получили сообщение компилятора, утверждающее, что файл с расширением
.lib
невозможно найти, то, возможно, вы сделали что-то не так при инсталлировании библиотеки. Внимательно проверьте п. 3, в котором указан путь для сохранения библиотечных файлов (.lib
) на вашем компьютере.• Если вы получили сообщение компилятора, утверждающее, что файл с расширением
.h
невозможно открыть, значит, скорее всего, вы ошиблись при инсталлировании. Внимательно проверьте п. 3, в котором указан путь для сохранения заголовочных файлов (.h
) на вашем компьютере.• Если вы получили сообщение редактора связей, упоминающее о неразрешенных внешних ссылках, то проблема таится в свойствах проекта.
Если наши советы вам не помогли, зовите друга.
Приложение ДРеализация графического пользовательского интерфейса
“Когда вы наконец поймете, что делаете,
то все пойдет правильно”
Билл Фэйрбэнк (Bill Fairbank)
В этом приложении представлена реализация обратных вызовов, а также классов
Window
, Widget
и Vector_ref
. В главе 16 мы не требовали от читателей знать об указателях и операторах приведения типа, поэтому вынесли подробные объяснения в приложение.Д.1. Реализация обратных вызовов
Обратные вызовы реализованы следующим образом:
void Simple_window::cb_next(Address, Address addr)
// вызов функции Simple_window::next() для окна,
// расположенного по адресу addr
{
reference_to(addr).next();
}
Поскольку вы уже прочитали главу 17, то вам должно быть очевидно, что аргумент
Address
должен иметь тип void*
. И, разумеется, функция reference_to(addr)
должна каким-то образом создавать ссылку на объект класса Simple_window
из указателя addr
, имеющего тип void*
. Однако, если у вас нет опыта программирования, то ничто для вас не “очевидно” и не “разумеется”, пока вы не прочтете главу 17, поэтому рассмотрим и использование адресов подробнее.Как описано в разделе A.17, язык C++ предлагает способ для указания имени типа. Рассмотрим пример.
typedef void* Address; // Address — это синоним типа void*
Это значит, что мы можем использовать имя
Address
вместо void*
. В данном случае, используя имя Address
, мы хотим подчеркнуть, что передаем адрес, и скрыть тот факт, что void*
— это имя типа указателя на объект, тип которого неизвестен.Итак, функция
cb_next()
получает указатель типа void*
с именем addr
в качестве аргумента и — каким-то образом — немедленно преобразовывает его в ссылку Simple_window&
:
reference_to(addr)
Функция
reference_to
является шаблонной (раздел A.13).
templateW& reference_to(Address pw)
// интерпретирует адрес как ссылку на объект класса W
{
return *static_cast(pw);
}
Здесь мы использовали шаблонную функцию, для того чтобы самостоятельно написать операции, действующие как приведение типа
void*
к типу Simple_window&
. Это приведение типа static_cast
описано в разделе 17.8.Компилятор не имеет возможности проверить наши предположения о том, что аргумент
addr
ссылается на объект класса Simple_window
, но правила языка требуют, чтобы компилятор в этом вопросе доверял программисту. К счастью, мы оказались правы. Об этом свидетельствует от факт, что система FLTK возвращает нам обратно указатель, который мы ей передавали. Поскольку, передавая указатель системе FLTK, мы знали его тип, можно использовать функцию reference_to
, чтобы “получить его обратно”. Все это немного запутанно, не проходит проверку и не больше характерно для низкоуровневого программирования.Получив ссылку на объект класса
Simple_window
, мы можем использовать ее для вызова функции-члена класса Simple_window
. Рассмотрим пример (раздел 16.3).
void Simple_window::cb_next(Address, Address pw)
// вызов функции Simple_window::next() для окна,
// расположенного по адресу pw
{
reference_to(pw).next();
}
Мы использовали довольно сложную функцию обратного вызова
cb_next()
, просто чтобы согласовать типы, необходимые для вызова совершенно обычной функции-члена next()
.Д.2. Реализация класса Widget
Наш интерфейсный класс
Widget
выглядит следующим образом.
class Widget {
// Класс Widget — это дескриптор класса Fl_widget,
// а не сам класс Fl_widget;
// мы пытаемся не смешивать наши интерфейсные классы с FLTK
public:
Widget(Point xy, int w, int h, const string& s, Callback cb)
:loc(xy), width(w), height(h), label(s), do_it(cb)
{ }
virtual ~Widget() { } // деструктор
virtual void move(int dx,int dy)
{ hide(); pw–>position(loc.x+=dx, loc.y+=dy); show(); }
virtual void hide() { pw–>hide(); }
virtual void show() { pw–>show(); }
virtual void attach(Window&) = 0; // каждый объект класса
// Widget определяет хотя бы
// одно действие над окном
Point loc;
int width;
int height;
string label;
Callback do_it;
protected:
Window* own; // каждый объект класса Widget
// принадлежит объекту классу Window
Fl_Widget* pw; // каждый объект класса Widget о "своем"
// классе Fl_Widget
};
Обратите внимание на то, что наш класс
Widget
следит за “своим” компонентом библиотеки FLTK и классом Window
, с которыми он связан. Кроме того, отметьте, что для этого нам необходимы указатели, поскольку объект класса Widget
на протяжении времени своего существования может быть связан с разными объектами класса Window
. Ссылки или именованного объекта для этого недостаточно. (Объясните почему?)Объект класса
Widget
имеет местоположение (loc
), прямоугольную форму (width
и height
), а также сметку (label
. Интересно, что он также имеет функцию обратного вызова (do_it
), т.е. связывает образ объекта класса Widget
на экране с фрагментом своего кода. Смысл операций move()
, show()
, hide()
и attach()
должен быть очевидным.Класс
Widget
выглядит незаконченным. Он спроектирован как класс реализации, который пользователи не должны видеть слишком часто. Его стоит переделать. Мы подозреваем, что все эти открытые члены и “очевидные” операции содержат подводные камни.Класс
Widget
имеет виртуальную функцию и может быть использован как базовый класс, поэтому в нем предусмотрен виртуальный деструктор (см. раздел 17.5.2).Д.3. Реализация класса Window
Когда следует использовать указатели, а когда ссылки? Мы обсудили этот общий вопрос в разделе 8.5.6. Здесь мы лишь отметим, что некоторые программисты любят указатели и что нам нужны указатели, когда мы хотим сослаться на разные объекты в разные моменты времени.
До сих пор мы скрывали главный класс в нашей графической библиотеке — класс
Window
. Основная причина этого заключалась в том, что он использует указатели, а его реализация с помощью библиотеки FLTK опирается на использование свободной памяти. Вот как описан этот класса в заголовочном файле Window.h
.
class Window : public Fl_Window {
public:
// позволяет системе выбрать место в памяти:
Window(int w, int h, const string& title);
// верхний левый угол в точке xy:
Window(Point xy, int w, int h, const string& title);
virtual ~Window() { }
int x_max() const { return w; }
int y_max() const { return h; }
void resize(int ww, int hh) { w=ww, h=hh; size(ww,hh); }
void set_label(const string& s) { label(s.c_str()); }
void attach(Shape& s) { shapes.push_back(&s); }
void attach(Widget&);
void detach(Shape& s); // удаляет элемент w из фигур
void detach(Widget& w); // удаляет элемент w из окна
// (отключает обратные вызовы)
void put_on_top(Shape& p); // помещает объект p поверх
// всех других фигур
protected:
void draw();
private:
vector shapes; // фигуры связываются с окном
int w,h; // размер окна
void init();
};
Итак, когда мы связываем фигуру с окном, используя функцию
attach()
, мы храним указатель в объектах класса Shape
, поэтому объект класса Window
может рисовать соответствующую фигуру. Поскольку впоследствии мы можем отсоединить фигуру от окна с помощью функции detach()
, поэтому нам нужен указатель. По существу, присоединенная фигура принадлежит своему коду; мы просто передаем объекту класса Window
ссылку на нее. Функция Window::attach()
преобразовывает свой аргумент в указатель, чтобы его можно было сохранить. Как показано выше, функция attach()
является тривиальной; функция detach()
немного сложнее. Открыв файл Window.cpp
, мы видим следующее.
void Window::detach(Shape& s)
// определяет, что первой должна быть удалена
// последняя присоединенная фигура
{
for (unsigned int i = shapes.size(); 0
if (shapes[i–1]==&s) shapes.erase(&shapes[i–1]);
}
Функция-член
erase()
удаляет (стирает) значение из вектора, уменьшая его размер на единицу (раздел 20.7.1). Класс Window
используется как базовый, поэтому он содержит виртуальный деструктор (раздел 17.5.2).Д.4. Реализация класса Vector_ref
По существу, класс
Vector_ref
имитирует вектор ссылок. Мы можем инициализировать его ссылками или указателями.• Если объект передается объекту класса
Vector_ref
с помощью ссылки, то предполагается, что он принадлежит вызывающей функции, которая управляет его временем жизни (например, объект — это переменная, находящаяся в определенной области видимости).• Если объект передается объекту класса
Vector_ref
с помощью указателя, то предполагается, что он размещен в памяти с помощью оператора new
, а ответственность за его удаление несет класс Vector_ref
.
Элемент хранится в объекте класса
Vector_ref
в виде указателя, а не как копия объекта, и имеет семантику ссылок. Например, можно поместить в вектор класса Vector_ref
объект класса Circle
, не подвергаясь опасности срезки.
template class Vector_ref {
vector v;
vector owned;
public:
Vector_ref() {}
Vector_ref(T* a, T* b = 0, T* c = 0, T* d = 0);
~Vector_ref() { for (int i=0; i
delete owned[i]; }
void push_back(T& s) { v.push_back(&s); }
void push_back(T* p) { v.push_back(p); owned.push_back(p); }
T& operator[](int i) { return *v[i]; }
const T& operator[](int i) const { return *v[i]; }
int size() const { return v.size(); }
};
Деструктор класса
Vector_ref
удаляет каждый объект, переданный ему как указатель.Д.5. Пример: манипулирование объектами класса Widget
Это законченная программа. Она демонстрирует многие из свойств классов
Widget/Window
. Мы поместили в нее минимальное количество комментариев. К сожалению, такое недостаточное комментирование программ — довольно распространенное явление. Попытайтесь выполнить эту программу и объяснить, как она работает.
#include "../GUI.h"
using namespace Graph_lib;
class W7 : public Window {
// четыре способа продемонстрировать, что кнопка может
// передвигаться:
// показать/скрыть, изменить местоположение, создать новую
// и присоединить/отсоединить
public:
W7(int n, int n, const string& t);
Button* p1; // показать/скрыть
Button* p2;
bool sh_left;
Button* mvp; // переместить
bool mv_left;
Button* cdp; // создать/уничтожить
bool cd_left;
Button* adp1; // активировать/деактивировать
Button* adp2;
bool ad_left;
void sh(); // действия
void mv();
void cd();
void ad();
static void cb_sh(Address, Address addr) // обратные вызовы
{ reference_to(addr).sh(); }
static void cb_mv(Address, Address addr)
{ reference_to(addr).mv(); }
static void cb_cd(Address, Address addr)
{ reference_to(addr).cd(); }
static void cb_ad(Address, Address addr)
{ reference_to(addr).ad(); }
};
Однако объект класса
W7
(эксперимент с объектом класса Window
номер 7
) на самом деле содержит шесть кнопок: просто две из них он скрывает.
W7::W7(int w, int h, const string& t)
:Window(w,h,t),
sh_left(true),mv_left(true),cd_left(true),ad_left(true)
{
p1 = new Button(Point(100,100),50,20,"show",cb_sh);
p2 = new Button(Point(200,100),50,20,"hide",cb_sh);
mvp = new Button(Point(100,200),50,20,"move",cb_mv);
cdp = new Button(Point(100,300),50,20,"create",cb_cd);
adp1 = new Button(Point(100,400),50,20,"activate",cb_ad);
adp2 = new Button(Point(200,400),80,20,"deactivate",cb_ad);
attach(*p1);
attach(*p2);
attach(*mvp);
attach(*cdp);
p2–>hide();
attach(*adp1);
}
В этом классе существуют четыре обратных вызова. Каждый из них проявляется в том, что нажатая кнопка исчезает и вместо нее появляется новая. Однако это достигается четырьмя разными способами.
voidW7::sh() // скрывает кнопку, показывает следующую
{
if (sh_left) {
p1–>hide();
p2–>show();
}
else {
p1–>show();
p2–>hide();
}
sh_left = !sh_left;
}
void W7::mv() // перемещает кнопку
{
if (mv_left) {
mvp–>move(100,0);
}
else {
mvp–>move(–100,0);
}
mv_left = !mv_left;
}
void W7::cd() // удаляет кнопку и создает новую
{
cdp–>hide();
delete cdp;
string lab = "create";
int x = 100;
if (cd_left) {
lab = "delete";
x = 200;
}
cdp = new Button(Point(x,300), 50, 20, lab, cb_cd);
attach(*cdp);
cd_left = !cd_left;
}
void W7::ad() // отсоединяет кнопку от окна и
// устанавливает связь с ее заменой
{
if (ad_left) {
detach(*adp1);
attach(*adp2);
}
else {
detach(*adp2);
attach(*adp1);
}
ad_left = !ad_left;
}
int main()
{
W7 w(400,500,"move");
return gui_main();
}
Эта программа демонстрирует основные способы добавления и удаления элементов окна, которые проявляются в их исчезновении и появлении.
Глоссарий