Программирование на языке Ruby.

12.3. FXRuby (FOX).

FOX — относительно новая технология, упор в ней сделан на быстродействие и межплатформенную совместимость. В значительной степени совместимость обусловлена самодостаточностью — в отличие от многих систем, это не обертка платформенного API.

Сама система написана на языке C++, хотя привязки можно создать практически для любого языка (для Ruby они уже имеются). Поскольку система изначально объектно-ориентированная, она хорошо сопрягается с Ruby и довольно естественно расширяется.

Технология FOX не так широко распространена, как Tk или GTK+, но популярна в среде программистов на Ruby. Отчасти это обусловлено наличием великолепной привязки FXRuby (см. сайт http://fxruby.org). FXRuby — плод трудов Лайла Джонсона (Lyle Johnson), который немало сделал для поддержки и документирования библиотеки. Он же в течение многих лет предоставляет техническую поддержку и оказал неоценимую помощь при написании этого раздела.

12.3.1. Обзор.

FXRuby — это привязка к Ruby библиотеки FOX, написанной на C++. В нее входит много классов для разработки полноценных графических приложений. Хотя аббревиатура FOX означает Free Objects for X (Бесплатные объекты для X), она была успешно перенесена и на другие платформы, включая MS Windows. Лайл Джонсон написал привязку FOX к Ruby, а также перенес саму библиотеку на платформу Windows. Исходную версия библиотеки FOX разработал Джероен ван дер Зийп (Jeroen van der Zijp) при поддержке компании CFD Research Corporation.

Виджеты FOX обладают современным внешним обликом. По полноте они могут соперничать с платформенными интерфейсами, в том числе и с MS Windows, при этом располагая возможностями, сильно превосходящими многие другие библиотеки виджетов.

Библиотеку классов FOX легко освоит программист, знакомый с другими средствами разработки графических интерфейсов. API не содержит зависимостей от платформы. Поскольку FOX написана на C++, некоторые аспекты API FxRuby сохраняют влияние статической природы и соглашений, принятых в C++ (например, перечисления и поразрядные операции).

Центральным механизмом, упрощающим работу с FOX, является парадигма сообщение/получатель. Любой объект в FOX — это экземпляр класса FXObject или одного из его подклассов. Определяемые пользователем объекты также должны наследовать одному из этих классов. Любой экземпляр FXObject может посылать и получать сообщения. Сообщение связывается к конкретным получателем во время выполнения в момент отправки.

Внутри FOX сообщение представляется типом, идентификатором и данными. Классы FOX пользуются общим набором определений сообщений, что позволяет виджетам взаимодействовать.

Обработчик сообщения должен вернуть 1, если сообщение обработано, и 0 в противном случае. FOX не перенаправляет необработанные сообщения другим виджетам неявно. Возвращаемое значение используется для того, чтобы понять, нужно ли обновлять интерфейс. Приложение FXRuby могло бы воспользоваться возвращаемым значением, чтобы самостоятельно перенаправить необработанные сообщения и тем самым реализовать паттерн Chain of Responsibility (цепочка обязанностей), описанный в книге E. Gamma, R. Helm, R. Johnson, J. Vlissides «Design Patterns»[14].

Еще один механизм FOX — парадигма автоматического обновления. Неявный цикл обработки событий в FOX включает фазу обновления, в которой объекты FOX могут обработать сообщения об обновлении. Обычно обработчик такого сообщения изменяет внешний вид того или иного виджета, основываясь на текущем состоянии данных приложения. Например, программа, показанная в листинге 12.9 (см. раздел 12.3.3), имеет кнопку, которая обновляет собственное состояние «активна/не активна» в зависимости от значения некоторой переменной.

12.3.2. Простое оконное приложение.

Вот пример минимального приложения FXRuby, которое делает то же самое, что рассмотренные выше приложения Tk и GTK+:

Require 'fox16' # Используются привязки к FOX 1.6.

Include Fox.

Application = FXApp.new.

Main = FXMainWindow.new(application, "Today's Date").

Str = Time.now.strftime("&Today is %B %d, %Y").

Button = FXButton.new(main, str).

Button.connect(SEL_COMMAND) { application.exit }.

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Этого примера достаточно для демонстрации двух важнейших классов FXRuby: FXApp и FXMainWindow. Приложение должно в самом начале создать и инициализировать объект FXApp. FXMainWindow — подкласс FXTopWindow; каждый виджет в FOX — некая разновидность «окна». Класс FXTopWindow представляет окно верхнего уровня, которое появляется непосредственно на экране. Более сложное приложение FXRuby обычно создает подкласс FXMainWindow и размещает в нем виджеты на этапе инициализации.

Конструктору FXMainWindow необходимо передать первым параметром объект FXApp. Второй параметр — заголовок окна. По умолчанию экземпляр FXMainWindow размещается в центре экрана и снабжается всеми стандартными элементами, присущими FXTopWindow. Таким образом, для окна отображается полоса заголовка с кнопками свертывания, развертывания и закрытия. Его размеры можно изменять.

Атрибут decorations главного окна позволяет явно указать необходимые элементы оформления. Например, можно запретить изменение размеров:

Main = FXMainWindow.new(application, "Today's Date").

Main.decorations = DECOR_TITLE | DECOR_CLOSE.

Значение decorations образуется комбинированием битовых флагов, как это принято в C++. В примере выше окно имеет только заголовок и кнопку закрытия.

В этом простом примере главное окно содержит всего один виджет — экземпляр класса FXButton, в котором отображается текущая дата.

Str = Time.now.strftime("&Today is %B %d, %Y").

Button = FXButton.new(main, str).

Первый аргумент конструктора FXButton — родительское окно, содержащее данный виджет. В нашем примере это главное окно. Второй аргумент — текст, рисуемый на кнопке.

В следующей строчке показано, как с помощью метода connect ассоциировать с кнопкой блок:

Button.connect(SEL_COMMAND) { application.exit }.

Здесь говорится, что когда кнопка отправляет командное сообщение (то есть сообщение типа SEL_COMMAND), следует вызвать метод exit.

В оставшихся строчках мы наблюдаем «ритуал обручения» объектов FXApp и FXMainWindow:

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Любое приложение FXRuby должно включать подобные строки, чтобы создать экземпляр приложения, показать окно FXMainWindow и запустить цикл обработки событий. Аргумент PLACEMENT_SCREEN метода show определяет, в каком месте экрана должно появиться окно. Из других возможных значений упомянем PLACEMENT_CURSOR (поместить окно там, где находится курсор), PLACEMENT_OWNER (в центре окна-владельца) и PLACEMENT_MAXIMIZED (раскрыть окно на весь экран).

12.3.3. Кнопки.

Вы уже видели, как организуется работа с кнопками в FXRuby. Заглянем немного глубже.

На кнопке может размещаться не только короткая строка. Допустимы и несколько строк, разделенных символом новой строки:

Text = "&Hello, World!\n" +

 "Do you see multiple lines of text?".

FXButton.new(self, text).

Обратите внимание на амперсанд перед буквой H в строке "Hello, World!". Он задает «горячую клавишу», нажатие которой эквивалентно щелчку по кнопке.

На кнопке может быть также нарисовано изображение, заданное в разных форматах. Например:

Text = "&Неllо, World!\n" +

 "Do you see the icon?\n" +

 "Do you see multiple lines of text?".

Icon = File.open("some_icon.gif", "rb") do |file|

 FXGIFIcon.new(app, file.read).

End.

FXButton.new(self, text, icon).

В листинге 12.9 иллюстрируется механизм обновления состояния интерфейса, реализованный в FOX:

Листинг 12.9. Обновление состояния интерфейса в FOX.

Require 'fox16'.

Include Fox.

Class TwoButtonUpdateWindow < FXMainWindow.

 def initialize(app).

  # Сначала инициализируем базовый класс.

  super(app, "Update Example", nil, nil,

  DECOR_TITLE | DECOR_CLOSE).

  # Первая кнопка:

  @button_one = FXButton.new(self, "Enable Button 2").

  @button_one_enabled = true.

  # Вторая кнопка:

  @button_two = FXButton.new(self, "Enable Button 1").

  @button_two.disable.

  @button_two_enabled = false.

  # Устанавливаем обработчики сообщений.

  @button_one.connect(SEL_COMMAND, method(:onCommand)).

  @button_two.connect(SEL_COMMAND, method(:onCommand)).

  @button_one.connect(SEL_UPDATE, method(:onUpdate)).

  @button_two.connect(SEL_UPDATE, method(:onUpdate)).

 end.

 def onCommand(sender, sel, ptr).

  # Обновить состояние приложения.

  @button_one_enabled = !@button_one_enabled.

  @button_two_enabled = !@button_two_enabled.

 end.

 def onUpdate(sender, sel, ptr).

  # Обновить кнопки в зависимости от состояния приложения.

  @button_one_enabled ?

  @button_one.enable : @button_one.disable.

  @button_two_enabled ?

  @button_two.enable : @button_two.disable.

 end.

End.

Application = FXApp.new.

Main = TwoButtonUpdateWindow.new(application).

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Здесь в главное окно добавлено две кнопки. Мы снова воспользовались методом connect, чтобы связать сообщение SEL_COMMAND от кнопок с кодом, но на этот раз код представляет собой метод, а не блок:

@button_one.connect(SEL_COMMAND, method(:onCommand)).

В этом примере мы встречаем еще один тип сообщения — SEL_UPDATE. Такое сообщение позволяет сделать виджеты независимыми друг от друга и от кода приложения. Как видим, ни одна кнопка не подозревает о существовании другой. Первая кнопка обновляет состояние второй, посылая сообщение обработчикам, которые отвечают за изменение состояния.

12.3.4. Текстовые поля.

FOX располагает полезными средствами для ввода текста. В следующем примере демонстрируется применение класса FXTextField для редактирования одной строки. Параметры определяют формат текста. Значение TEXTFIELD_PASSWD скрывает текст, являющийся паролем, TEXTFIELD_REAL позволяет вводить только действительные числа в научной нотации, a TEXTFIELD_INTEGER — только целые числа.

Simple = FXTextField.new(main, 20, nil, 0,

 JUSTIFY_RIGHT|FRAME_SUNKEN|

 FRAME_THICK|LAYOUT_SIDE_TOP).

Simple.text = "Simple Text Field".

Passwd = FXTextField.new(main, 20, nil, 0,

 JUSTIFY_RIGHT|TEXTFIELD_PASSWD|

 FRAME_SUNKEN|FRAME_THICK|

 LAYOUT_SIDE_TOP).

Passwd.text = "Password".

Real = FXTextField.new(main, 20, nil, 0,

 TEXTFIELD_REAL|FRAME_SUNKEN|

 FRAME_THICK|LAYOUT_SIDE_TOP|

 LAYOUT_FIX_HEIGHT, 0, 0, 0, 30).

Real.text = "1.0E+3".

Int = FXTextField.new(main, 20, nil, 0, TEXTFIELD_INTEGER|

 FRAME_SUNKEN|FRAME_THICK|

 LAYOUT_SIDE_TOP|LAYOUT_FIX_HEIGHT,

 0, 0, 0, 30).

Int.text = "1000".

Ниже показан простой способ ввода текста с помощью диалогового окна. В зависимости от выбранного метода можно будет вводить любой текст, только действительные или только целые числа.

Puts FXInputDialog.getString("initial text",

 self, "Диалог для ввода текст",

 "Введите текст:", nil).

Puts FXInputDialog.getInteger(1200, self,

 "Диалог для ввода целого числа",

 "Введите целое число:", nil).

Puts FXInputDialog.getReal(1.03е7, self,

 "Диалог для ввода числа в научной нотации",

 "Введите действительное число:", nil).

Для экономии места мы не станем приводить полный текст приложения. Но, конечно, перед выводом диалогового окна необходимо выполнить обычную инициализацию.

12.3.5. Прочие виджеты.

В следующем примере демонстрируется использование меню и полос меню в приложениях. Отметим, что объекты FXMenuCommand следуют общей для FOX парадигме сообщение/получатель, с которой мы уже сталкивались при работе с кнопками:

Require 'fox16'.

Include Fox.

Application = FXApp.new.

Main = FXMainWindow.new(application, "Simple Menu").

Menubar = FXMenuBar.new(main, LAYOUT_SIDE_TOP |

 LAYOUT_FILL_X).

Filemenu = FXMenuPane.new(main).

Quit_cmd = FXMenuCommand.new(filemenu, "&Quit\tCtl-Q").

Quit_cmd.connect(SEL_COMMAND) { application.exit }.

FXMenuTitie.new(menubar, "&File", nil, filemenu).

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Здесь и FXMenuBar, и FXMenuPane добавляются непосредственно в главное окно FXMainWindow. Благодаря параметрам LAYOUT_SIDE_TOP и LAYOUT_FILL_X полоса меню размещается в верхней части родительского окна и простирается от левой до правой границы. Текст команды меню "&Quit\tCtl-Q" подразумевает, что комбинация клавиш Alt+Q играет роль «горячей клавиши», a Ctrl+Q — клавиши быстрого выбора пункта меню. Последовательное нажатие Alt+F и Alt+Q эквивалентно щелчку по меню File с последующим выбором пункта Quit. Нажатие Ctrl+Q заменяет всю последовательность.

В классе FXTopWindow есть метод для свертывания главного окна. Следующие три строчки добавляют в меню File команду, которая свернет окно:

FXMenuCommand.new(filemenu, "&Icon\tCtl-I") do |cmd|

Cmd.connect(SEL_COMMAND) { main.minimize } end.

На этом примере мы видим еще один прием, полезный при конструировании команды меню. Если вам не нужна ссылка на виджет, представляющий команду меню, то можно просто присоединить блок к вызову FXMenuCommand.new и выполнить всю инициализацию виджета внутри блока. Разумеется, этот прием применим к любому встроенному в FOX классу.

В листинге 12.10 демонстрируются переключатели.

Листинг 12.10. Переключатели в FOX.

Require 'fox16'.

Include Fox.

Class RadioButtonHandlerWindow < FXMainWindow.

 def initialize(app).

  # Invoke base class initialize first.

  super(app, "Radio Button Handler", nil, nil,

   DECOR_TITLE | DECOR_CLOSE).

  choices = [ "Good", "Better", "Best" ].

  group = FXGroupBox.new(self, "Radio Test Group",

   LAYOUT_SIDE_TOP |

   FRAME_GROOVE |

   LAYOUT_FILL_X).

  choices.each do |choice|

   FXRadioButton.new(group, choice,

    nil, 0,

    ICON_BEFORE_TEXT |

    LAYOUT_SIDE_TOP).

  end.

 end.

End.

Application = FXApp.new.

Main = RadioButtonHandlerWindow.new(application).

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Группы переключателей — стандартное средство в графических приложениях, предназначенное для выбора одного из взаимно исключающих вариантов. В данном примере варианты представлены массивом из трех строк:

Choices = [ "Good", "Better", "Best" ].

В главное окно добавляется объект FXGroupBox, который визуально указывает, что три переключателя взаимосвязаны, а затем в этот контейнер добавляются сами переключатели (по одному на каждый вариант). Но сам контейнер FXGroupBox ничего не делает для того, чтобы обеспечить взаимное исключение. Если запустить пример в таком виде, то вы сможете выбрать более одного переключателя.

Есть несколько способов обеспечить ожидаемое поведение переключателей, но в приложениях FOX чаще всего для этой цели используют получатель данных — класс FXDataTarget. Это специальный объект, играющий роль хранителя какого-то значения. Как и любой другой объект в FOX, FXDataTarget может посылать и получать сообщения.

Программа в листинге 12.11 — модифицированный вариант предыдущей, в ней демонстрируется применение получателей данных.

Листинг 12.11. Переключатели в FOX и получатели данных.

Require 'fox16'.

Include Fox.

Class RadioButtonHandlerWindow < FXMainWindow.

 def initialize(app).

  # Сначала вызвать инициализатор базового класса.

  super(app, "Radio Button Handler", nil, nil,

   DECOR_TITLE | DECOR_CLOSE).

  choices = [ "Good", "Better", "Best" ].

  default_choice = 0.

  @choice = FXDataTarget.new{default_choice).

  group = FXGroupBox.new(self, "Radio Test Group",

   LAYOUT_SIDE_TOP |

   FRAME_GROOVE |

   LAYOUT_FILL_X).

  choices.each_with_index do |choice, index|

   FXRadioButton.new(group, choice,

    @choice, FXDataTarget::ID_OPTION+index,

    ICON_BEFORE_TEXT |

    LAYOUT_SIDE_TOP).

  end.

 end.

End.

Application = FXApp.new.

Main = RadioButtonHandlerWindow.new(application).

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

В этом примере @choice — экземпляр FXDataTarget, значением которого является целочисленный индекс выбранного в данный момент положения переключателя. Получатель данных инициализирован нулем, что соответствует элементу «Good» массива choices.

При конструировании каждого переключателя задается получатель данных, а идентификатор сообщения от переключателя делается равным FXDataTarget::ID_OPTION плюс желаемое значение. Если теперь запустить пример, то вы увидите, что переключатель стал вести себя как положено.

Для добавления в окно списка FXList и его инициализации тоже достаточно нескольких строк. Значение LIST_BROWSESELECT позволяет выбирать из списка ровно один элемент. В начальный момент выбран самый первый из них. Значение LIST_SINGLESELECT допускает выбор не более одного элемента; в этом случае в начальный момент ни один элемент не выбран:

@list = FXList.new(self, nil, 0,

 LIST_BROWSESELECT |

 LAYOUT_FILL_X).

@names = ["Chuck", "Sally", "Franklin", "Schroeder",

 "Woodstock", "Matz", "Lucy"].

@names.each { |name| @list.appendItem(name) }.

Отметим, что вместо метода appendItem можно использовать оператор вставки в массив, то есть последнюю строку можно было бы записать и так:

@names.each { |name| @list << name }.

Весь пример целиком приведен в листинге 12.12. Сообщение обрабатывается в главном окне, в результате выводится выбранный элемент. Если был задан режим LIST_SINGLE_SELECT, то важно отличать щелчок, при котором элемент был выбран, от щелчка, который отменил выбор.

Листинг 12.12. Виджет FXList.

Require 'fox16'.

Include Fox.

Class ListHandlerWindow < FXMainWindow.

 def initialize(app).

  # Сначала вызвать инициализатор базового класса.

  super(app, "List Handler", nil, nil,

   DECOR_TITLE | DECOR_CLOSE).

  @list = FXList.new(self, nil, 0,

   LIST_BROWSESELECT |

   LAYOUT_FILL_X).

  @list.connect(SEL_COMMAND) do |sender, sel, pos|

   puts pos.to_s + " => " + @names[pos].

  end.

  @names = ["Chuck", "Sally", "Franklin",

   "Schroeder", "Woodstock",

   "Matz", "Lucy"].

  @names.each { |name| @list << name }.

 end.

End.

Application = FXApp.new.

Main = ListHandlerWindow.new(application).

Application.create.

Main.show(PLACEMENT_SCREEN).

Application.run.

Если вместо LIST_BROWSESELECT поставить LIST_EXTENDEDSELECT, то в списке можно будет выбирать несколько элементов:

@list = FXList.new(self, nil, 0, LIST_EXTENDEDSELECT | LAYOUT_FILL_X).

Обработчик сообщений можно изменить так, чтобы он отображал все выбранные элементы. Чтобы понять, какие элементы списка выбраны, придется перебрать все:

@list.connect(SEL_COMMAND) do |sender, sel, pos|

 puts "Был щелчок по " + pos.to_s +"=>" +

  @names[pos].

 puts "Выбраны следующие элементы:".

 @list.each do |item|

  if item.selected?

   puts " " + item.text.

  end.

 end.

End.

Атрибут numVisible объекта FXList позволяет указать, сколько элементов списка видно одновременно. Существует также виджет FXListBox, который отображает только выбранное значение. Его интерфейс похож на интерфейс FXList с несколькими отличиями. Аргументы конструктора точно такие же, как видно из следующего примера. Отметим, что FXListBox позволяет выбирать только один элемент, поэтому значение LIST_EXTENDEDSELECT игнорируется:

@list_box = FXListBox.new(self,nil,0,LIST_BROWSESELECT | LAYOUT_FILL_X).

@names = ["Chuck", "Sally", "Franklin", "Schroeder",

 "Woodstock", "Matz", "Lucy"].

@names.each { |name| @list_box << name }.

Диалоговое окно можно определить один раз как подкласс класса FXDialogBox, а затем использовать для создания модальных или немодальных диалогов. Однако способы взаимодействия модальных и немодальных диалогов со своим владельцем различны.

Под модальным мы понимаем окно или диалог, который препятствует доступу к другим частям приложения, пока не будет закрыт. Немодальный диалог позволяет передавать фокус другим окнам приложения.

В следующем примере определяется класс модального и немодального диалога. Для модального класса используются предопределенные сообщения ID_CANCEL и ID_ACCEPT. Немодальный класс пользуется только предопределенным сообщением ID_HIDE.

Для отображения немодального диалога применяется уже знакомый метод FXTopwindow.show. Модальный диалог имеет собственный цикл обработки событий, отличный от цикла всего приложения. Для его отображения служит метод FXDialogBox.execute. Как видно из полного листинга программы, значение, возвращаемое методом execute, зависит от того, какое значение было передано методу приложения stopModal для завершения цикла обработки событий модального диалога. В этом примере значение 1 говорит о том, что пользователь нажал кнопку Accept.

Modal_btn.connect do.

 dialog = ModalDialogBox.new(self).

 if dialog.execute(PLACEMENT_OWNER) == 1.

  puts dialog.text.

 end.

End.

Немодальный диалог работает параллельно с другими окнами приложения. Приложение должно запрашивать интересующие его данные у диалога по мере необходимости. Один из способов известить о появлении новых данных - включить в диалог кнопку Apply (Применить), которая будет посылать зависящее от приложения сообщение главному окну. В примере ниже используется также таймер — еще одна интересная особенность FxRuby. Когда таймер срабатывает, главному окну посылается сообщение. Обработчик этого сообщения (показан ниже) запрашивает у диалога новое значение и взводит таймер еще на одну секунду:

Def onTimer(sender, sel, ptr).

 text = @non_modal_dialog.text.

 unless text == @previous.

  @previous = text.

  puts @previous.

 end.

 getApp().addTimeout(1000, method(:onTimer)).

End.

В листинге 12.13 приведен полный текст примера использования модальных и немодальных диалогов.

Листинг 12.13. Модальные и немодальные диалоги.

Require 'fox16'.

Include Fox.

Class NonModalDialogBox < FXDialogBox.

 def initialize(owner).

  # Сначала вызвать инициализатор базового класса.

  super(owner, "Test of Dialog Box",

   DECOR_TITLE|DECOR_BORDER).

  text_options = JUSTIFY_RIGHT | FRAME_SUNKEN |

   FRAME_THICK | LAYOUT_SIDE_TOP.

  @text_field = FXTextField.new(self, 20, nil, 0,

   text_options).

  @text_field.text = "".

  layout_options = LAYOUT_SIDE_TOP | FRAME_NONE |

   LAYOUT_FILL_X | LAYOUT_FILL_Y |

   РАСК_UNIFORM_WIDTH.

  layout = FXHorizontalFrame.new(self, layout_options).

  options = FRAME_RAISED | FRAME_THICK |

   LAYOUT_RIGHT | LAYOUT_CENTER_Y.

  hide_btn = FXButton.new(layout, "&Hide", nil, nil, 0,

   options).

  hide_btn.connect(SEL_COMMAND) { hide }.

 end.

 def text.

  @text_field.text.

 end.

End.

Class ModalDialogBox < FXDialogBox.

 def initialize(owner).

  # Сначала вызвать инициализатор базового класса.

  super(owner, "Test of Dialog Box",

   DECOR_TITLE|DECOR_BORDER).

  text_options = JUSTIFY_RIGHT | FRAME_SUNKEN |

   FRAME_THICK | LAYOUT_SIDE_TOP.

  @text_field = FXTextField.new(self, 20, nil, 0,

   text_options).

  @text_field.text = "".

  layout.options = LAYOUT_SIDE_TOP | FRAME_NONE |

   LAYOUT_FILL_X | LAYOUT_FILL_Y |

   PACK_UNIFORM_WIDTH.

  layout = FXHorizontalFrame.new(self, layout_options).

  options = FRAME_RAISED | FRAME_THICK |

   LAYOUT_RIGHT | LAYOUT_CENTER_Y.

  cancel_btn = FXButton.new(layout, "&Cancel", nil,

   self, 0, options).

  cancel_btn.connect(SEL_COMMAND) do.

   app.stopModal(self, 0).

   hide.

  end.

  accept_btn = FXButton.new(layout, "&Accept", nil,

   self, 0, options).

  accept_btn.connect(SEL_COMMAND) do.

   app.stopModal(self, 1).

   hide.

  end.

 end.

 def text.

  @text_field.text.

 end.

End.

Class DialogTestWindow < FXMainWindow.

 def initialize(app).

  # Сначала инициализировать базовый класс.

  super(app, "Dialog Test", nil, nil,

   DECOR_ALL, 0, 0, 400, 200).

  layout_options = LAYOUT_SIDE_TOP | FRAME_NONE |

   LAYOUT_FILL_X | LAYOUT_FILL_Y |

   PACK_UNIFORM_WIDTH.

  layout = FXHorizontalFrame.new(self, layout_options).

  button_options = FRAME_RAISED | FRAME_THICK |

   LAYOUT_CENTER_X | LAYOUT_CENTER_Y.

  nonmodal_btn = FXButton.new(layout, "&Non-Modal Dialog...", nil,

   nil, 0, button_options).

  nonmodal_btn.connect(SEL_COMMAND) do.

   @non_modal_diаlоg.show(PLACEMENT_OWNER).

  end.

  modal_btn = FXButton.new(layout, "&Modal Dialog...", nil,

   nil, 0, button_options).

  modal_btn.connect(SEL_COMMAND) do.

   dialog = ModalDialogBox.new(self).

   if dialog.execute(PLACEMENT_OWNER) == 1.

    puts dialog.text.

   end.

  end.

  getApp.addTimeout(1000, method(:onTimer)).

  @non_modal_dialog = NonModalDialogBox.new(self).

 end.

 def onTimer(sender, sel, ptr).

  text = @non_modal_dialog.text.

  unless text == @previous.

   @previous = text.

   puts @previous.

  end.

  getApp.addTimeout(1000, method(:onTimer)).

 end.

 def create.

  super.

  show(PLACEMENT_SСRЕЕN).

 end.

End.

Application = FXApp.new.

DialogTestWindow.new(application).

Application.create.

Application.run.

Перед началом длинного вычисления в FXRuby следует заменить текущий курсор курсором ожидания, а по завершении восстановить исходный. В классе FXApp есть два удобных метода, позволяющих изменить курсор без явного запоминания предыдущего: beginWaitCursor и endWaitCursor. Если метод beginWaitCursor вызывается в блоке, то по выходе из блока будет автоматически вызван метод endWaitCursor:

GetApp.beginWaitCursor do.

# Выполнить длительную операцию...

End.

12.3.6. Дополнительные замечания.

Библиотека FOX располагает еще многими виджетами и возможностями, например: деревья, стыкуемые панели инструментов, всплывающие подсказки, строки состояния и страницы с вкладками. К числу более сложных средств следует отнести перетаскивание между приложениями и получатели данных, упрощающие связывание данных с виджетами. В библиотеке имеются также неграфические средства для поддержки кросс-платформенного программирования, в том числе класс FXRegistry.

Для связи между приложением и его окружением можно использовать сигналы, а также различные каналы ввода и вывода, которые транслируются в сообщения, посылаемые объектам FOX.

Имеются виджеты, поддерживающие наиболее распространенные графические форматы, а также API для работы с библиотекой OpenGL. Это не просто дань вежливости трехмерной графике: на базе библиотеки FOX C++ было реализовано немало инженерных приложений.

Учитывая все вышесказанное, библиотеку FXRuby можно считать мощным и гибким инструментом. В последние несколько лет она приобрела популярность в сообществе пользователей Ruby; ожидается, что число поклонников будет расти и дальше. Возможности библиотеки быстро изменяются и расширяются, самую актуальную информацию о привязках к Ruby можно найти на сайте http://fxruby.org.