Monday, May 12, 2014

Playing with Multiple Windows

This tutorial is the first practice session for the topic of CPWindows, which is discussed in the overview found here: CPWindow, the start of all View Hierarchies.

Create a new Project

First, create a new project.  If you installed the developer's edition of Cappuccino, it is as simple as navigating to your web root directory and typing "capp gen WindowStyle".  If you downloaded the StarterKit, then copy the example "Hello World" application to a new directory, and name the directory "WindowStyle."  Regardless of whether you used capp or copied the starter kit directory, loading the new Cappuccino page in your browser should display the "Hello World" text that we will learn to expect from all new Cappuccino projects.

Creating Multiple Windows

Using your favorite text editor, edit the AppController.j file.  Remember the lessons from Playing with Window Style, and change the window to a Textured window with CGRectMake(20,40,300,100).  Copy the line and paste it two times.  Change the variable names to theWindow2 and theWindow3.

In order to see theWindow2 and theWindow3 after they load, choose new locations for the CGRects.  I chose (300,40) and (300,140) as the new locations.

Let's also change theWindow3 to a CPPanel and use a CPHUDBackgroundWindowMask, so that we can distinguish the CPPanel from the CPWindows.

The code should now look like this:

var the Window = [[CPWindow alloc] initWithContentRect:CGRectMake(20,40,300,
100) styleMask:CPTexturedBackgroundWindowMask|CPClosableWindowMask|CPResizableW
indowMask],
the Window = [[CPWindow alloc] initWithContentRect:CGRectMake(20,40,300,
100) styleMask:CPTexturedBackgroundWindowMask|CPClosableWindowMask|CPResizableW
indowMask],
the Window = [[CPWindow alloc] initWithContentRect:CGRectMake(20,40,300,
100) styleMask:CPTexturedBackgroundWindowMask|CPClosableWindowMask|CPResizableW
indowMask],
contentView = [theWindow contentView];
When we reload the page, this is what we see:

Our new windows don't appear.  Why not?

CPWindows must be told to display themselves.  We do this by calling the orderFront method. The orderFront method takes an object as an argument.  For the purpose of this tutorial, the object that we pass is irrelevant.  The Photo Album tutorial uses the "self" object, regardless of where the method is being called from.  We will pass the "nil" object.

Add the following lines to your code:

[theWindow2 orderFront:nil];
[theWindow3 orderFront:nil];
Now, when you reload your page, you will see the following:

Play with the windows.  Drag them around the screen.  You will notice that the CPWindows keep moving to the front when they are clicked.  The CPPanel however remains on the level that it is activated on.

Reopening a Window that is Closed


Ideally, one should use a WindowController for windows that need to be reopened.  The WindowController performs additional checks during the process, but it is possible to cause a window to re-display without a WindowController, and we will look at that method here:

We are going to make changes so that we can close and re-open these windows.  A window cannot be opened without a reference to the object that defines it, so the first change we will make is storing the objects by making the variables part of the AppController implementation:
@implementation AppController : CPObject
{
        CPWindow Window1;
        CPWindow Window2;
        CPPanel  thePanel;
}
In the applicationDidFinishLaunching function, add the following lines of code:

    Window1 = theWindow;
    Window2 = theWindow2;
    thePanel = theWindow3; 
Now, from any method within the AppController object, we can refer to our windows using these object attributes: Window1, Window2, thePanel.

We are going to add a borderless bridge window now with three buttons to call our windows when we need them. We are not going to cover buttons in this tutorial, so I will just give you some code to add, and we will discuss the controller objects in a later tutorial.

To the applicationDidFinishLaunching: method, add the following lines of code:
    var borderlessBridge = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask];
    var button1 = [CPButton buttonWithTitle:@"Window 1"];
    var button2 = [CPButton buttonWithTitle:@"Window 2"];
    var button3 = [CPButton buttonWithTitle:@"Panel"];
    var button4 = [CPButton buttonWithTitle:@"New Window"];
    [button1 setFrameOrigin:CGPointMake(20, 100)];
    [button2 setFrameOrigin:CGPointMake(20, 200)];
    [button3 setFrameOrigin:CGPointMake(20, 300)];
    [button4 setFrameOrigin:CGPointMake(20, 400)];
    [button1 setTarget:self];
    [button1 setAction:@selector(showWindow1:)];
    [button2 setTarget:self];
    [button2 setAction:@selector(showWindow2:)];
    [button3 setTarget:self];
    [button3 setAction:@selector(showPanel:)];
    [button4 setTarget:self];
    [button4 setAction:@selector(newWindow:)];
  
    var contentView = [borderlessBridge contentView];
    [contentView addSubview:button1];
    [contentView addSubview:button2];
    [contentView addSubview:button3];
    [contentView addSubview:button4];
    [borderlessBridge orderFront:nil];


 Then, add three new methods to the AppController:
-(void)showWindow1:(id)aSender
{
    [Window1 orderFront:self];
    alert( [Window1 level] );
}
-(void)showWindow2:(id)aSender
{
    [Window2 orderFront:self];
    alert( [Window2 level] );
}
-(void)showPanel:(id)aSender
{
    [thePanel orderFront:self];
    alert( [thePanel level] );
}
-(void)newWindow:(id)aSender
{
    var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake( 20,400,50,50 )
        styleMask:CPTexturedBackgroundWindowMask|CPClosableWindowMask|CPResizableWindowMask];
    [theWindow orderFront:self];
}

When you reload the window, you will see three buttons now.  The buttons, when clicked call the methods we created: showWindow1, showWindow2, and showPanel.

Now, as before, we are still able to move the windows about and close them using the close buttons.  However, when we need to get a window back, we can click the corresponding button to retrieve it.  You will notice in the methods that we added, that to get the windows back that we just call "orderFront" on the existing window object, and it re-draws itself.

The keys to re-opening a closed window is:

  1. having a link that points to the window object
  2. calling the orderFront method on the object
If a window is already open, we can click the corresponding button that we have created, and we will see that a new window is not created.  We can change the size of a window and close it, and clicking the corresponding window redisplays it with the same resized dimensions that we set before we closed the window.

The "New Window" button is a different story altogether. When we click that button, we see that a new window is created each time that we click the button.  If we change the dimensions of a window, it has no effect on the next window that is created.  We can also use the button to create multiple new windows without closing the previously created window.  The other buttons however, continue to recall the original windows that were created.

Earlier we noted that the CPWindow objects moved to the front of the viewing area when we click on the window, but the CPPanel does not.  We can use the same button that we use to "reopen" the CPPanel to bring it to the front of the view stack.  We also can use the setLevel method to manipulate the visual layer on which the window sits, although we are not playing with that method on this particular tutorial.

In this tutorial, we played with the concept of opening multiple windows at one time, starting to see some of the differences between CPWindow and CPPanel, we also saw how we can redisplay a closed window.


No comments:

Post a Comment