3. Replacing the Controller class

The Controller class is the smallest of the three classes, so let’s convert it first. Here is the Objective-C source, extracted from Controller.h and Controller.m. Note that the class has one outlet and one action. These are used in the nib file to connect the controller with the window and slider.

Original Controller source code [Objective-C]
@interface Controller : NSObject
{
    IBOutlet NSWindow *itsWindow;
}
- (IBAction)changeTransparency:(id)sender;
@end

@implementation Controller
- (IBAction)changeTransparency:(id)sender
{	
    //set the window's alpha value from 0.0-1.0
    [itsWindow setAlphaValue:[sender floatValue]];
    //go ahead and tell the window to redraw things, which has the effect of calling CustomView's -drawRect: routine
    [itsWindow display];
}
@end

Now let’s make the straightforward conversion to Ruby. First, under Xcode’s File menu, use the New File… item to add an empty file to your project. Name it RTW.rb (or anything you like that has the ”.rb” extension); we can put all the Ruby code for the RoundTransparentWindow conversion in this one file. Then put this code in your new Ruby file:

Controller [ruby]
class Controller < OSX::NSObject
    ib_outlet :itsWindow
    def changeTransparency(sender)
       @itsWindow.setAlphaValue(sender.floatValue)
       @itsWindow.display
    end
end

Comment out the Objective-C Controller class, but keep it around in case you need to switch back for testing. Now build and test it. How does everything look?

Nearly everything should be working fine after your change. The application should run as before, with a round window with transparent corners. Moving the slider should change the window’s overall transparency. But if you are testing carefully, you’ve noticed that our window no longer changes shape when the transparency drops below 0.7. What happened?

If you’re a ruby expert, you may have noticed the error in our changeTransparency function. But I didn’t. After I noticed the change, I switched back to the Objective-C version to see if I could reproduce the problem. There wasn’t much else to try, so I commented out the call to the display function:
// [itsWindow display];
When I rebuilt and retested the Objective-C version, I found that it too exhibited the same problem. So apparently the display function was not getting called correctly. Why not?

Accomplished rubyists know that Ruby objects are all derived from a class called Object, and that this class already has a function named display. As a result, our translated Cocoa code calls this function instead of the one that we want, the display method of NSWindow. We can override this by adding the oc_ prefix to our method name; this forces the bridge to use the Objective-C version. So that solves our problem in a way. But it’s very dissatisfying to realize that when we made this mistake we received no warning from the bridge. In fact, it had no opportunity to warn us because it never was informed of the call. So be sure to test your RubyCocoa applications carefully and watch out for name conflicts.

Here are two other mistakes that I made as I rushed through the translation:

  • I incorrectly used ”<<” instead of ”<” for inheritance. That was just sloppiness on my part; most ruby users know that ”<” is the symbol to use when declaring the parent of a class.
  • I added an underscore at the end of my changeTransparency method name, thinking that because the framework bridges method calls named both changeTransparency_ and changeTransparency, I could use either in my method definition. But in fact, this method gets attached to the slider in the nib file, and is called from Cocoa framework code. When RubyCocoa bridges the connection, it looks for a method named without the underscore. In this case, RubyCocoa’s convenient “more than one way to do it” way of bridging Objective-C and Ruby method names becomes misleading. I personally prefer the consistent, but perhaps uglier way that PyObjC always requires underscores, but in the RubyCocoa world, it’s generally best to omit the final underscore. Fortunately, the application does generate a warning message when this problem occurs:
2006-03-01 17:52:11.225 RoundTransparentWindow[17712] Could not connect the action changeTransparency: to target of class Controller

It’s a lot easier to diagnose problems like these when you catch them early; so be sure to test early and often as you work.

Did you find an error? Is something missing? Post your comment or suggestion below!

Comments (0) post