In the 0.7.x version series, Glimmer DSL for LibUI finally gets the much awaited Table Selection API and Table Sorting API that got added to the underlying libui C library a few months ago. The good news is tables are now sortable by default without having to write a single line of code (Convention Over Configuration). That means all older applications that had tables now have sortable tables. That said, sorting logic can be customized or disabled if needed. Also, table selection is enabled by default, but to consume the selection, an `on_selection_changed` listener can be nested under the `table` control.
The Table Selection API includes the following `table` properties and listeners:
- `selection_mode` (`Symbol`) [default: `:zero_or_one`]: sets selection mode to `:one`, `:zero_or_one`, `:zero_or_many`, or `:none`
- `selection` (`Integer` or `Array` of `Integer`s): a single `Integer` row index for `:one` and `:zero_or_one` selection modes, or an `Array` of `Integer` row indexes if selection mode is `:zero_or_many`
- `on_row_clicked {|table, row| }`: triggered upon clicking a table row
- `on_row_double_clicked {|table, row| }`: triggered upon double-clicking a table row
- `on_selection_changed {|table, selection, added_selection, removed_selection| }`: triggered upon selecting a table row
Code mini-example of table single-selection:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Source: https://github.com/AndyObtiva/glimmer-dsl-libui/ | |
table { | |
... | |
selection_mode :one # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified) | |
selection 2 # initial selection row index (could be nil too or just left off, defaulting to 0) | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} |
Code mini-example of table multi-selection:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Source: https://github.com/AndyObtiva/glimmer-dsl-libui/ | |
table { | |
... | |
selection_mode :zero_or_many # other values are :none , :zero_or_one , and :one (default is :zero_or_one if not specified) | |
selection 0, 2, 4 # initial selection row indexes (could be empty array too or just left off) | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
removed_selection&.each do |selected_row| | |
@zero_or_many_table_selection_checkboxes[selected_row].checked = false | |
end | |
added_selection&.each do |selected_row| | |
@zero_or_many_table_selection_checkboxes[selected_row].checked = true | |
end | |
end | |
} |
The Table Sorting API includes the following for the `table` control:
- `sortable` (Boolean) [default: `true`]: enables automatic table sorting support
- `header_visible` (Boolean): shows or hides column headers
Additionally, if there is a need to do custom sorting, `sortable` could be set to `false`, and the following properties/listeners could be nested underneath `table` column controls (e.g. inside `text_column(name) {}`):
- `sort_indicator` (`Symbol`): sets sort indicator to ascending or descending with the value being `:ascending`, `:descending`, `:asc`, `:desc`, `:a`, or `:d`
- `on_clicked` (`Proc`): this listener is triggered when a table column is clicked so that it can handle sorting
Code mini-example of custom table sorting:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
table { | |
text_column('Animal') { | |
sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default) | |
on_clicked do |tc, column| | |
sort_one_table_column(tc, column) | |
end | |
} | |
text_column('Description') { | |
sort_indicator :descending # (optional) can be :ascending, :descending, or nil (default) | |
on_clicked do |tc, column| | |
sort_one_table_column(tc, column) | |
end | |
} | |
... | |
# header_visible true # default | |
sortable false # disable default sorting behavior to demonstrate manual sorting | |
} |
Here is a full code example of table selection and sorting done automatically with reliance on data-binding:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Source: https://github.com/AndyObtiva/glimmer-dsl-libui | |
require 'glimmer-dsl-libui' | |
class BasicTableSelection | |
TableColumnPresenter = Struct.new(:name, | |
:column, | |
:table_presenter, | |
keyword_init: true) | |
TablePresenter = Struct.new(:data, | |
:column_names, | |
:selection_mode, | |
:selection, | |
:header_visible, | |
:sortable, | |
keyword_init: true) do | |
def selection_items | |
data.size.times.map { |row| "Row #{row} Selection" } | |
end | |
def toggle_header_visible | |
self.header_visible = !(header_visible.nil? || header_visible) | |
end | |
def toggle_sortable | |
self.sortable = !(sortable.nil? || sortable) | |
end | |
def column_presenters | |
@column_presenters ||= column_names.each_with_index.map do |column_name, column| | |
TableColumnPresenter.new(name: column_name, column: column, table_presenter: self) | |
end | |
end | |
def selected_row | |
selection && data[selection] | |
end | |
def selected_rows | |
selection && selection.is_a?(Array) && selection.map { |row| data[row] } | |
end | |
end | |
include Glimmer::LibUI::Application | |
before_body do | |
data = [ | |
%w[cat meow], | |
%w[dog woof], | |
%w[chicken cock-a-doodle-doo], | |
%w[horse neigh], | |
%w[cow moo] | |
] | |
@one_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :one, # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified) | |
selection: 2, # initial selection row index (could be nil too or just left off, defaulting to 0) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@zero_or_one_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :zero_or_one, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified) | |
selection: nil, # initial selection row index (could be an integer too or just left off, defaulting to nil) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@zero_or_many_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :zero_or_many, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified) | |
selection: [0, 2, 4], # initial selection row index (could be an integer too or just left off, defaulting to nil) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@none_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :none, # other values are :zero_or_many , :zero_or_one, :one (default is :zero_or_one if not specified) | |
selection: nil, # defaults to nil | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
end | |
body { | |
window('Basic Table Selection', 400, 300) { | |
tab { | |
tab_item('One') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@one_table_selection_radio_buttons = radio_buttons { | |
items @one_table_presenter.selection_items | |
selected <=> [@one_table_presenter, :selection] | |
} | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@one_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@one_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@one_table = table { | |
@one_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @one_table_presenter.data | |
selection_mode <= [@one_table_presenter, :selection_mode] | |
selection <=> [@one_table_presenter, :selection] | |
header_visible <= [@one_table_presenter, :header_visible] | |
sortable <= [@one_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('Zero-Or-One') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@zero_or_one_table_selection_radio_buttons = radio_buttons { | |
items @zero_or_one_table_presenter.selection_items | |
selected <=> [@zero_or_one_table_presenter, :selection] | |
} | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@zero_or_one_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@zero_or_one_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@zero_or_one_table = table { | |
@zero_or_one_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @zero_or_one_table_presenter.data | |
selection_mode <= [@zero_or_one_table_presenter, :selection_mode] | |
selection <=> [@zero_or_one_table_presenter, :selection] | |
header_visible <= [@zero_or_one_table_presenter, :header_visible] | |
sortable <= [@zero_or_one_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('Zero-Or-Many') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@zero_or_many_table_selection_checkboxes = @zero_or_many_table_presenter.data.size.times.map do |row| | |
checkbox("Row #{row} Selection") { | |
checked <=> [@zero_or_many_table_presenter, :selection, | |
on_read: ->(selection_rows) {selection_rows.to_a.include?(row)}, | |
on_write: ->(checked_value) { | |
checked_value ? | |
(@zero_or_many_table_presenter.selection.to_a + [row]).uniq : | |
@zero_or_many_table_presenter.selection.to_a.reject {|v| v == row } | |
}, | |
] | |
} | |
end | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@zero_or_many_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@zero_or_many_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@zero_or_many_table = table { | |
@zero_or_many_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @zero_or_many_table_presenter.data | |
selection_mode <= [@zero_or_many_table_presenter, :selection_mode] | |
selection <=> [@zero_or_many_table_presenter, :selection] | |
header_visible <= [@zero_or_many_table_presenter, :header_visible] | |
sortable <= [@zero_or_many_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('None') { | |
vertical_box { | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@none_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@none_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@none_table = table { | |
@none_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @none_table_presenter.data | |
selection_mode <= [@none_table_presenter, :selection_mode] | |
selection <=> [@none_table_presenter, :selection] | |
header_visible <= [@none_table_presenter, :header_visible] | |
sortable <= [@none_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
} | |
} | |
} | |
} | |
} | |
} | |
end | |
BasicTableSelection.launch |
Here is a full code example of table selection and sorting done manually without data-binding:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Source: https://github.com/AndyObtiva/glimmer-dsl-libui | |
require 'glimmer-dsl-libui' | |
class BasicTableSelection | |
TableColumnPresenter = Struct.new(:name, | |
:column, | |
:table_presenter, | |
keyword_init: true) | |
TablePresenter = Struct.new(:data, | |
:column_names, | |
:selection_mode, | |
:selection, | |
:header_visible, | |
:sortable, | |
keyword_init: true) do | |
def selection_items | |
data.size.times.map { |row| "Row #{row} Selection" } | |
end | |
def toggle_header_visible | |
self.header_visible = !(header_visible.nil? || header_visible) | |
end | |
def toggle_sortable | |
self.sortable = !(sortable.nil? || sortable) | |
end | |
def column_presenters | |
@column_presenters ||= column_names.each_with_index.map do |column_name, column| | |
TableColumnPresenter.new(name: column_name, column: column, table_presenter: self) | |
end | |
end | |
def selected_row | |
selection && data[selection] | |
end | |
def selected_rows | |
selection && selection.is_a?(Array) && selection.map { |row| data[row] } | |
end | |
end | |
include Glimmer::LibUI::Application | |
before_body do | |
data = [ | |
%w[cat meow], | |
%w[dog woof], | |
%w[chicken cock-a-doodle-doo], | |
%w[horse neigh], | |
%w[cow moo] | |
] | |
@one_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :one, # other values are :zero_or_many , :zero_or_one, :none (default is :zero_or_one if not specified) | |
selection: 2, # initial selection row index (could be nil too or just left off, defaulting to 0) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@zero_or_one_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :zero_or_one, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified) | |
selection: nil, # initial selection row index (could be an integer too or just left off, defaulting to nil) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@zero_or_many_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :zero_or_many, # other values are :zero_or_many , :one, :none (default is :zero_or_one if not specified) | |
selection: [0, 2, 4], # initial selection row index (could be an integer too or just left off, defaulting to nil) | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
@none_table_presenter = TablePresenter.new( | |
data: data.dup, | |
column_names: ['Name', 'Description'], | |
selection_mode: :none, # other values are :zero_or_many , :zero_or_one, :one (default is :zero_or_one if not specified) | |
selection: nil, # defaults to nil | |
header_visible: nil, # defaults to true | |
sortable: nil, # defaults to true | |
) | |
end | |
body { | |
window('Basic Table Selection', 400, 300) { | |
tab { | |
tab_item('One') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@one_table_selection_radio_buttons = radio_buttons { | |
items @one_table_presenter.selection_items | |
selected <=> [@one_table_presenter, :selection] | |
} | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@one_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@one_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@one_table = table { | |
@one_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @one_table_presenter.data | |
selection_mode <= [@one_table_presenter, :selection_mode] | |
selection <=> [@one_table_presenter, :selection] | |
header_visible <= [@one_table_presenter, :header_visible] | |
sortable <= [@one_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('Zero-Or-One') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@zero_or_one_table_selection_radio_buttons = radio_buttons { | |
items @zero_or_one_table_presenter.selection_items | |
selected <=> [@zero_or_one_table_presenter, :selection] | |
} | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@zero_or_one_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@zero_or_one_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@zero_or_one_table = table { | |
@zero_or_one_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @zero_or_one_table_presenter.data | |
selection_mode <= [@zero_or_one_table_presenter, :selection_mode] | |
selection <=> [@zero_or_one_table_presenter, :selection] | |
header_visible <= [@zero_or_one_table_presenter, :header_visible] | |
sortable <= [@zero_or_one_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('Zero-Or-Many') { | |
vertical_box { | |
vertical_box { | |
stretchy false | |
@zero_or_many_table_selection_checkboxes = @zero_or_many_table_presenter.data.size.times.map do |row| | |
checkbox("Row #{row} Selection") { | |
checked <=> [@zero_or_many_table_presenter, :selection, | |
on_read: ->(selection_rows) {selection_rows.to_a.include?(row)}, | |
on_write: ->(checked_value) { | |
checked_value ? | |
(@zero_or_many_table_presenter.selection.to_a + [row]).uniq : | |
@zero_or_many_table_presenter.selection.to_a.reject {|v| v == row } | |
}, | |
] | |
} | |
end | |
} | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@zero_or_many_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@zero_or_many_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@zero_or_many_table = table { | |
@zero_or_many_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @zero_or_many_table_presenter.data | |
selection_mode <= [@zero_or_many_table_presenter, :selection_mode] | |
selection <=> [@zero_or_many_table_presenter, :selection] | |
header_visible <= [@zero_or_many_table_presenter, :header_visible] | |
sortable <= [@zero_or_many_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
on_selection_changed do |t, selection, added_selection, removed_selection| | |
# selection is an array or nil if selection mode is zero_or_many | |
# otherwise, selection is a single index integer or nil when not selected | |
puts "Selection Changed: #{selection.inspect}" | |
puts "Added Selection: #{added_selection.inspect}" | |
puts "Removed Selection: #{removed_selection.inspect}" | |
end | |
} | |
} | |
} | |
tab_item('None') { | |
vertical_box { | |
horizontal_box { | |
stretchy false | |
button('Toggle Table Header Visibility') { | |
on_clicked do | |
@none_table_presenter.toggle_header_visible | |
end | |
} | |
button('Toggle Table Sortability') { | |
on_clicked do | |
@none_table_presenter.toggle_sortable # toggles sortable attribute to false or true | |
end | |
} | |
} | |
@none_table = table { | |
@none_table_presenter.column_presenters.each do |column_presenter| | |
text_column(column_presenter.name) | |
end | |
cell_rows @none_table_presenter.data | |
selection_mode <= [@none_table_presenter, :selection_mode] | |
selection <=> [@none_table_presenter, :selection] | |
header_visible <= [@none_table_presenter, :header_visible] | |
sortable <= [@none_table_presenter, :sortable] | |
on_row_clicked do |t, row| | |
puts "Row Clicked: #{row}" | |
end | |
on_row_double_clicked do |t, row| | |
puts "Row Double Clicked: #{row}" | |
end | |
} | |
} | |
} | |
} | |
} | |
} | |
end | |
BasicTableSelection.launch |
No comments:
Post a Comment