AddThis Social Bookmark Button

Print

Programming With Cocoa
An Introduction to RubyCocoa, Part 2

by Christopher Roach
10/12/2004

Editor's note -- In Part 1 of this two-part series, Christopher Roach provided some background and helped you get started with RubyCocoa programming. In today's conclusion he gets into the actual code ... and if you're following along, you'll end up with a functioning application.

Creating a Skeleton

The first thing we'll need to do before we can begin coding our application is to add a new Ruby file. We'll need to add a class that inherits from the NSObject class. To do so, control-click on the "Classes" directory under "Group & Files" in Xcode. Select "Add->New File..." from the pop-up menu, and then select Ruby NSObject subclass under Ruby in the "New File" dialogue.

Click "Next," and title your new Ruby class Controller.rb, or whatever you titled your controller class in Interface Builder (IB). Then add the new class to the current project and target and click "Finish." Once you've done this you should have a skeleton for a Ruby class that inherits from the NSObject class found in the RubyCocoa OSX module. Next we'll need to add our outlets to the new class.

We add outlets to a Ruby class by calling the ns_outlets method and passing to it a list of symbols representing the names of the outlets we wish to create. This should look very similar to calling the attr_writer method that Ruby provides for creating writer methods for instance variables. You could just as easily substitute attr_writer for the ns_outlets method and the application would still work without incident. You can also use the alias ib_outlets to replace the ns_outlets method as I do in my code.

After we've added all five of our IB outlets to our new Ruby class, we need to add the methods that correspond to the actions we created in IB. We do this by creating a method for each of the actions using the same name as the action we created that takes one parameter for the sender of the message. Once we have added all five actions, we should have a class skeleton similar to the following.

require 'osx/cocoa'

class Controller < OSX::NSObject
	include OSX

	ib_outlets :archiveFile, :fileTypeView,
		:fileTableView, :fileType, :mainWindow

	def addFile(sender)
		puts "addFile method"
	end

	def removeFile(sender)
		puts "removeFile method"
	end

	def browseForArchive(sender)
		puts "browseForArchive method"
	end

	def createArchive(sender)
		puts "createArchive method"
	end

	def extractArchive(sender)
		puts "extractArchive method"
	end
end
O'Reilly Mac OS X Conference

There are two things that you should notice in the code above that are different from what I've already told you to add to your new file. First, I have added a line to each method that prints a string to the console window. This is strictly for testing. If you like, you can go ahead and run your application and try out each of the buttons to make sure that each one works properly. Afterward, make sure you remove each of the puts statements so that our finished application is not trying to print to the console every time we invoke an action.

Next, you'll notice that I've added a line to the beginning of the class definition that includes the OSX module found in the RubyCocoa framework. If you'll remember back at the beginning of this tutorial, I stated that every class in the RubyCocoa framework can be found in the OSX module. What we've done here by including it in our class is to mix-in (Ruby's method for avoiding multiple inheritance) all of these classes, methods, and variables into our class, giving us direct access to everything that the RubyCocoa framework has to offer.

So, you've tried out your new RubyCocoa application and everything seems to be working fine so far, right? And, you've also gone back through the code and scrubbed it by removing all of those unnecessary puts commands, correct? Great, then you've got everything ready for our final section in which we add all of the rest of the class's internals and get our application fully functioning.

Adding the Guts

We'll start by setting up the methods that will be used to manipulate our NSTableView object. The two methods we need to implement for this are the addFile and removeFile methods. However, before we can get these methods working properly we'll need to set up an object to act as our data source for the NSTableView instance.

Our application is simple enough that we will not be creating a separate data structure to act as the data source for our NSTableView. Instead, our Controller will be acting as our data source. To do this, we need to implement two methods in our Controller class as a minimum to allow it to act as a NSTableView data source. These two methods are the numberOfRowsInTableView method that returns the number of files in our table and the tableView_objectValueForTableColumn_row method that returns the value found in the cell currently selected in our table. Below is the implementation for each of these methods.

###
# numberOfRowsInTableView
#  Returns the number of records in the table.
#  This must be implemented by whatever class
#  acts as the data source for the NSTableView
#  class.
###
def numberOfRowsInTableView(afileTable)
	@files.length
end

###
# tableView_objectValueForTableColumn_row
#  Returns the value corresponding to the cell
#  (row and column intersection) the user has
#  currently selected. This must be
#  implemented by whatever class acts as the
#  data source for the NSTableView class.
###
def tableView_objectValueForTableColumn_row(
	afileTable, aTableColumn, rowIndex)
	@files[rowIndex]
end

Both of these methods are pretty simple for our application. The first method simply returns the number of elements in an array called @files (we'll find out more about this array in a second). The second method normally takes the point of intersection between the column and row currently selected and returns a value representing the chosen cell. However, our application only has a single column, so that makes the column number unnecessary in discovering the value of the chosen cell. All we need to do is return the element found at the index correlating to the currently selected row, which is exactly what we do in the code above. Now we just need to create an instance of the Array class and assign it our @files instance variable.

Our @files array that we use to hold the names of the files to be found in our application's table view object needs to be created before our two new methods are called. The best way to do this is to add an initialize method (Ruby's class constructor) and create an instance of the Array class there, just like the code below shows.

###
# initialize
#  This is the Ruby constructor for a class.
#  We use it to create a new
#  instance of the Array class to hold records
#  for our files table.
###
def initialize
	@files = Array.new
end

We now have our necessary methods for acting as a data source for the NSTableView object and we have created the array that will hold the information displayed in that table, but we still haven't implemented the addFile and removeFile methods that will manipulate the table. Lets go ahead and do that now.

First, add the following implementation to our addFile method:

###

# addFile

#  Displays an instance of the NSOpenPanel and

#  gets the name and location of one or more

#  files that the user wishes to add to the

#  archive.

###

def addFile(sender)

	oPanel = NSOpenPanel.openPanel

	oPanel.setAllowsMultipleSelection(true)

	buttonClicked = oPanel.runModal



	if buttonClicked == NSOKButton

		files = oPanel.filenames

		count = files.count



		for i in 0..count - 1

			@files.push(

				files.objectAtIndex(i))

		end



		@fileTableView.reloadData

	end

end

Pages: 1, 2, 3

Next Pagearrow