To demonstrate the simplest case, I will display some data from a CSV text file. For example, the letter recognition dataset from the UCI machine learning repository consists of two files. The file ending '.data' contains the raw data. Each line contains the class and attributes of an instance from the data set. Each instance can be represented using a Ruby struct. Taking the attribute names from the file ending '.names', I build the following struct:
LetterInstance = Struct.new(:letter, :x_box, :y_box, :width, :height,
:on_pixels, :x_bar, :y_bar, :x2bar, :y2bar,
:xybar, :x2ybar, :xy2bar, :x_ege, :xegvy,
:y_ege, :yegvx)
I define the Struct with the attribute names in the order that they occur in the CSV file, as this makes reading the CSV file simple. To read in the textfile to create an array of instances of this struct:
@@data = []
File.open("letter-recognition.data", "r") do |f|
f.each_line do |line|
@@data << LetterInstance.new(*line.strip.split(","))
end
end
Displaying the Data
Building the list control is almost trivial. I use two facts about Ruby Structs to help in constructing the table automatically. First, the method 'members' can be used to find all the field names within the Struct; these are used for the column headings.
For example: @@data[0].members.join(", ")
returns: "letter, x_box, y_box, width, height, on_pixels, x_bar, y_bar, x2bar, y2bar, xybar, x2ybar, xy2bar, x_ege, xegvy, y_ege, yegvx"
Second, fields within a Struct can be accessed using array indexes. So, @@data[0][0] returns the value in the 'letter' field of the first item.
The following few lines of code create a class for our own virtual list control, which extends Wx::ListCtrl. We are building a particular kind of list control, which uses 'report format' and a 'virtual list data model'. Other kinds exist, but are not covered here. The initialize method builds the columns automatically, and sets the number of items. The method 'on_get_item_text' is the one which makes this virtual list control so easy to use. The method is called only when an item is to be displayed. It is given the index of the item to display, and the column number. Your method just has to return the string for this item and column: as our data is stored as an array of structs, the implementation is as simple as can be.
# -- show data in a ListCtrl
require 'wx'
class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?
# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end
set_item_count @@data.size
end
# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end
end
class LetterDisplay < Wx::Frame
def initialize
super(nil, :title => "Letter Recognition Data")
LetterDataList.new self
end
end
Wx::App.run do
LetterDisplay.new.show
end
A screenshot of the result:

Handling Events
The list control supports a number of events. The most useful are for catching double clicks on a row or change in the highlighted row. Double clicks are known as activating the item, and single clicks as selecting the item. The event evt_list_item_activated captures a double click, and the event evt_list_item_selected captures a change in the focussed item (through a single click or use of arrow keys). Both events are easy to capture and redirect to a method to handle them. The following code will just print out the item(s) now selected by walking through the list of selected indices obtained by calling selections:
class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?
# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end
evt_list_item_selected(self) { selected_item }
set_item_count @@data.size
end
# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end
def selected_item
get_selections.each do |selection|
puts @@data[selection]
end
end
0 comments:
Post a Comment