The other day on the monomac IRC list there was a question on setting the FrameOrigin within MonoMac. The person was trying to convert and implement the Window Shake Effect program from – JustSayNo here. The effect is pretty awesome.

The effect uses Cocoa’s Animator proxy via an animation attached to the NSWindow’s frameOrigin property. So when the origin of the frame is set it will shake the window in place.

To follow along with the rest of this article you might want to download the following sample code below:

JustSayNo Sample code

My knee-jerk response was to use the MonoMac’s NSWindow method SetFrameOrigin(PointF origin) as follows:

   var location = Window.Frame.Location;  // A PointF class
   MyWindow.SetFrameOrigin(location);

To see this action load up the sample code project in MonoDevelop and run the project.

When the screen appears, press the button labeled Login. You will see the screen jump to the right. This is made possible by the code attached to the login action of the button.

48
49
50
51
52
53
54
		partial void login (NSButton sender)
		{
			var location = Window.Frame.Location;
			location.X += 50;
			Window.SetFrameOrigin(location);			
 
		}

Line 51 we get the current location of the window frame.
We then add 52 to the X coordinate of the frames position and the we set the new origin in line 53 using the SetFrameOrigin method of our Window.

To animate this movement using Cocoa we know we would need to execute this on the Animator Proxy property of the window instead of setting it directly. So on line 52 change the code to the following:

52
			((NSWindow)Window.Animator).SetFrameOrigin(location);

Notice the cast to NSWindow here. The Animator property returns an NSObject so to set the property itself you need to cast it to an NSWindow class or NSView view if you are dealing with NSViews.

Now run the program again and see what happens.

It disappears right? Humm What the heck is going on? That is really weird. This is also why I had to go ahead and convert the program myself to see if I could get it to work.

It dawned on me that I had seen these same types of issues while going through the samples in the book on core animation: Core Animation for Mac OS X and the iPhone: Creating Compelling Dynamic User Interfaces by Bill Dudney.

If you have read my other articles on KVC, KVO and Bindings you might already have thought of this. Well using KVC we already know how to set a property without directly accessing said property so out of curiosity I wanted to try that approach.

Go ahead and change line 52 with the following code:

52
			((NSWindow)Window.Animator).SetValueForKeyPath(NSValue.FromPointF(location),(NSString)"frameOrigin");

The variable location is a PointF object and not a NSObject so we have to wrap it up into an NSObject by using the NSValue.FromPoint method.

Now run the program again and you will see that we can now use the animator to set the frame’s origin value using the Animator Proxy property without the window disappearing. That gets us little bit closer to our objective.

Notice that we use SetValueForKeyPath and not SetValueForKey here. If you use the SetValueForKey you will get the following message.

2011-01-18 11:04:52.851 JustSayNo[20199:613] *** -[NSProxy methodSignatureForSelector:] called!

To actually animate this process you would use the following code:

		partial void login (NSButton sender)
		{
			var frame = Window.Frame;
			var location = frame.Location;
			location.X += 800;
			frame.Location = location;
			Window.SetFrame(frame, true, true);
		}

Now that we know how to set the frame origin via the animator let’s see if we can get the animation to work.

Within the downloaded project file there is a class file named ShakingWindow. Go ahead and open it.

The ShakingWindow class extends NSWindow. I broke it out into it’s own class so it could be reusable for multiple windows instead of implementing the shake effect in each window.

Now take a look at the method named shakeRattleAndRoll.

63
64
65
66
67
68
69
70
		internal void shakeRattleAndRoll()
		{
 
			// load our animations -- Note: this overwrites any other animations that may be attached
			// you may want to make this more robust
			Animations = NSDictionary.FromObjectAndKey(this.shakeAnimation(Frame),(NSString)"frameOrigin");
			Animator.SetValueForKeyPath(NSValue.FromPointF(Frame.Location),(NSString)"frameOrigin");
		}

Here we do the same thing as we just did in the previous examples by using the SetValueForKeyPath via the Animator Proxy.

First we load up our Animations with the key set to “frameOrigin” property.

To actually start the animations we need to set the frame origin so that is what we do in the next line.

Now to complete this example open up the MainWindow.cs file and make sure that it extends our custom NSWindow class ShakingWindow.

13
	public partial class MainWindow : ShakingWindow

And the final part is to actually call our shakeRattleAndRoll method. Open up the MainWindowController.cs file again and change the login method to call our shakeRattleAndRoll method.

48
49
50
51
		partial void login (NSButton sender)
		{
			Window.shakeRattleAndRoll();
		}

Now run the program again and click the Login button. You should now have the window shaking effect.

For a full discussion of the code take a look at the article here Window Shake Effect.

As a final note I have also provided the property accessors, NumberOfShakes, ShakeDuration and ShakeVigour on the ShakingWindow class if you want to play around with different values.