Friday, May 16, 2014

CPCheckBox: Implementing check boxes

This tutorial extends a previous tutorial on CPButtons.  Please review that tutorial before continuing on to this project.  We will use the same source files from that tutorial, and it is understood that you may have modified those files already while working on one of the other projects, the heart of this tutorial should remain unchanged.

We are going to use some concepts in this tutorial that we defined in the CPRadio tutorial.  For example, we are going to use CPOnState and CPOffState constants.  It is not necessary, but it is recommended that the CPRadio tutorial be done before this tutorial.

Unlike radio controls, check boxes stand, almost always, on their own.  This makes this tutorial much shorter.  To understand check boxes, we need only to know how to determine whether they are selected or unselected, and occasionally, we will need to know how to set the status programmatically.

Setting Up The Core

In order to check the status of the check box, we need to have a handle on the object. For the radio buttons, we cheated by letting the selection of the button trigger an action.  We can do the same here, but we need to consider alternative methods for storing variables.  For this exercise, we will save a pointer to the checkbox in the AppController attributes, and we will create three buttons.  We will not be covering the buttons in this tutorial.

Change the implementation definition to the following:
@implementation AppController : CPObject
{
     CPCheckBox checkBoxAttribute;
}
After the definition of checkBox in applicationDidFinishLaunching, add the following:
checkBoxAttribute = checkBox;

Checking the Status of the Check Box

To check the status and the value of a checkbox, we call the state and title methods of the CPCheckBox.  We will create a method to check the status of the checkbox using this information.  Add the following method to AppController:
-(void)checkStatus:(id)aButton
{
     var title = [checkBoxAttribute title];
     var status = [checkBoxAttribute state];
     var state;
     if( status == CPOnState ){
         state = @"Checkbox is selected.";
     }else if(status == CPOffState){
         state = @"Checkbox is not selected.";
     }else{
         state = @"Unhandled option";
     }
     var alertMessage = @"Value: " + title + @"\n" + state;
     alert( alertMessage );
}
We will verify that this checks the value and the state of the check box in a moment.  First, we will define the methods that select and deselect the checkbox.

Setting the Status of the Check Box

We can change the state of a check box by clicking on it, but there are times that you will need to preload a page with already selected values, and knowing how to set the state programmatically will be helpful.

To set the state, we will use the setState method, and we will use the constants CPOnState and CPOffState.  Add the following two methods to AppController:
-(void)setStateOn:(id)aButton
{
     [checkBoxAttribute setState:CPOnState];
}
-(void)setStateOff:(id)aButton
{
     [checkBoxAttribute setState:CPOffState];
}

Adding the Buttons

Let's add the buttons and we'll see how it all comes together.  Add the buttons by inserting the following code into the applicationDidFinishLaunching method after the contentValue variable's definition.
var button1 = [CPButton buttonWithTitle:@"Check Checkbox State"];
var button2 = [CPButton buttonWithTitle:@"Mark Checkbox"];
var button3 = [CPButton buttonWithTitle:@"Unmark Checkbox"];
[button1 setFrameOrigin:CPPointMake(100,300)];
[button2 setFrameOrigin:CPPointMake(300,300)];
[button3 setFrameOrigin:CPPointMake(500,300)];
[button1 setTarget:self]; [button1 setAction:@selector(checkStatus:)];
[button2 setTarget:self]; [button2 setAction:@selector(setStateOn:)];
[button3 setTarget:self]; [button3 setAction:@selector(setStateOff:)];
[contentView addSubview:button1];
[contentView addSubview:button2];
[contentView addSubview:button3];

Running the CheckBox Program

Run the program, and play with the buttons.  You will see that you are able to control the state of the checkbox with the buttons or directly. You will also see that you are able to check the current status of the checkbox.  I have included some screen shots here:


CPRadio: Implementing Radio Boxes

This tutorial extends a previous tutorial on CPButtons.  Please review that tutorial before continuing on to this project.  We will use the same source files from that tutorial, and it is understood that you may have modified those files already while working on one of the other projects, the heart of this tutorial should remain unchanged.

What sets our expectation of radio buttons apart from other controls is that we expect them to work together in groups.  As we look through the class file, we have the option of attaching it to a radioGroup, so we need to understand radio groups a little more before we continue.

CPRadioGroup

Image taken from: Cappuccino Project's API Documentation
The CPRadioGroup is not a control object.  It is also not a view object.  It is merely a means of grouping radio buttons together into one logical construct.

Because it is not a view subclass, the radio group has no visual display, and it has no effect on the display of the radio buttons within it.  We still must position those buttons manually.

CPRadioGroup has a method called _addRadio:(CPRadio), which is hidden from the documentation as a result of the '_' character which starts the name.  This function is difficult to find, because CPRadioGroup does not have its own definition file.  It is defined within the CPRadio.j file.

We can call _addRadio:(CPRadio) directly, but the developers hid this function for a reason.  Rather than use this method, we will define our radio group, and we will use the CPRadio method -(void)setRadioGroup:(CPRadioGroup).

For now, we will just create a group into which we will add our radio buttons.  Add the following to AppController:
var radioGroup = [[CPRadioGroup alloc] init];

Creating Our Buttons

Like we did before, we will create radio group buttons with titles.  We will create multiple this time in order to demonstrate the grouping option and see how to extract the selected value.  Add the following code after the radioGroup definition:

var radio1 = [CPRadio radioWithTitle:@"Option 1"], radio2 = [CPRadio radioWithTitle:@"Option 2"], radio3 = [CPRadio radioWithTitle:@"Option 3"];
[radio1 setFrameOrigin:CPPointMake(140,200)];
[radio2 setFrameOrigin:CPPointMake(140,215)];
[radio3 setFrameOrigin:CPPointMake(140,230)];
[radio1 setRadioGroup:radioGroup];
[radio2 setRadioGroup:radioGroup];
[radio3 setRadioGroup:radioGroup];
[contentView addSubview:radio1];
[contentView addSubview:radio2];
[contentView addSubview:radio3];

This creates:

Play with this interface, and you will discover that the three new option radios cannot be selected at the same time.  It is not possible, because when one is activated, it checks to see if it is part of a CPRadioGroup, and that CPRadioGroup cycles through all the other options to deselect them.  This of course does not need to be done by a CPRadioGroup option, we could do it by hand, but why would we want to?

We notice also, that the Radio button created in the parent tutorial is not affected by any of these changes, because it is not part of the CPRadioGroup.  We can change the option choice as many times as we like, and it will never clear the "Radio" choice at the top of the page.

Getting The Selected Value

setAction and setTarget

We do not need to use setAction or setTarget to get the value of a radio button, but here we have an opportunity to make our exploration more simple on ourselves.  We learned about setAction and setTarget methods when we explored CPButtons.  We learned then that setAction and setTarget were methods belonging to all CPControl objects, including CPRadio.

The good news is that the developers of Cappuccino recreated these methods in CPRadioGroup as well.  They behave the same way as we would expect them to behave, and like the CPButton example, we will keep things simple by using the AppController as the target.

Add this code:
[radioGroup setTarget:self];
[radioGroup setAction:@selector(getSelectedOption:)];

Remember to format the method selector properly by including the colon and omitting any quotations.  To ensure that we have complete code, we will also create an empty method at this time by adding the following to AppController:
-(void)getSelectedOption:(id)aRadioGroup
{
   //do something
}
I have named the method attribute "aRadioGroup" to remind us that we have attached this method to a radio group object, and therefore, we expect the calling object to be a radio group.  However, we can also attach this method to other objects as well, and the (id) reminds us that any object can be passed to this function.

Extracting the Selected CPRadio

CPRadioGroup has a function to return the selected item from its contained radio box items.  We are going to use the selectedRadio method to grab the CPRadio object.

Add the following to the getSelectedOption method:
var radioButton = [aRadioGroup selectedRadio];
Our variable radioButton now points to a CPRadio object, and all things going well, it should be the radio object that we have currently selected.  Let's test that theory by extracting the radio button text, and verifying that the radio button has a selected state.  Add the following next:
var title = [radioButton title];
var stateInt = [radioButton state];
alert( title );
alert( stateInt );
The title is a CPString and the state is recorded as a CPInteger, so we can pass them to the UI via a javascript alert function.  Even though seeing the int value of the state will not verify for us that the radio button is selected, because we don't know which integer value yet represents that state, we will at least see that we have made progress on our path of discovery.

Run the application and see what happens when you select an option.

When I run my application, I see the following:
First alert window to appear when application is run.



Second alert window to appear when application is run.
We can probably assume that the integer value of 1 indicates that the button is selected (correlating to the true value that might be used in other languages), but let's investigate further by cycling through all the options to see what values are set when an option is selected.

Add the following to the getSelectedOption method:
var allRadios = [aRadioGroup radios];
for( var i = 0; i < [allRadios count]; i++ ){
     var curRadio = allRadios[i];
     var title = [curRadio title];
     var status = [curRadio state];
     var messageString = "Radio Option (" + title + "):" + status;
     alert( messageString );
}
If you run the application again, you will see that no matter which radio option is selected, that this loop shows us the following values, not necessarily in the same order as depicted here:  1, 0, 0.  This confirms that 1 indicates a selected state, and the 0 indicates a non-selected state.

CPControl defines some constants that are used throughout the CPControl objects: CPOnState = 1, and CPOffState = 0.  This allows us to make our code a little more readable so that we can check a states value with one of the following more readable formats:
var buttonIsSelected = ([button state] == CPOnState);
var buttonIsNotSelected = ([button state] == CPOffState);
Note, if you try putting these lines of code in your application at this point, they won't do anything, because we have not defined the variable "button," and we also have done nothing with the resulting bool values.

CPButtons Extended: Checkboxes, Radio boxes, Disclosure buttons, and Pop-up buttons

This article is a closer look at text boxes as they relate to CPView objects.  Find other CPView articles, including additional articles on CPControl objects at the our root article on the CPView topic.

As we continue down the CPView rabbit hole, we discover that the CPButton class discussed in the last tutorial about CPButtons has children, and those children are rather useful.  From the Cappuccino Project's API documentation, we extract the following inheritance diagram:
Image Taken From: Cappuccino Project's API Documentation

Create a New Project

If you have followed some of the other tutorials, you should be comfortable with creating new projects.  Create a new application folder for this tutorial.  I have called my project CPButtonExtended.

Just like the previous tutorials, we will avoid creating additional object files in order to explore the basic objects.  These tutorials are short, and the objects we create are short-lived, so we will work entirely within the AppController.j file.

Starting with More of the Same

We are not going to explore these button types using the same long method that we usually use. Most of the exploration was done in the CPButton tutorial.  I do, however, want to demonstrate that these objects are just like all the other CPView objects we have worked with so far, and then we will use the quick and easy method to define our control interfaces to accelerate our development process.

Add the following code to AppController.j after the definition of contentView:
var checkBox = [[CPCheckBox alloc] initWithFrame:CGRectMake(40,80,40,40)];
[contentView addSubview:checkBox];
var radioBox = [[CPRadio alloc] initWithFrame:CGRectMake(80,80,40,40)];
[contentView addSubview:radioBox];
var disclosure = [[CPDisclosureButton alloc] initWithFrame:CGRectMake(120,80,40,40)];
[contentView addSubview:disclosure];
var popUp = [[CPPopUpButton alloc] initWithFrame:CGRectMake(160,80,40,40)];
[contentView addSubview:popUp];
Load your project in the browser:
From left to right, we have a check box, a radio box, a disclosure box and a pop-up box.  You may be more familiar with the pop-up box as a combo box or a selection box.  The disclosure box seems to be the root of a collapsable tree.  We will explore it more later.

We can add text labels and play with the CGRect definitions in order to frame everything correctly, or we can use the quick and easy method to define our objects with the label.  Just like the CPButton class added the buttonWithTitle method to make our lives easy, these classes have helper methods.  We will use them now.  Change the code that we added to the following:
var checkBox = [CPCheckBox checkBoxWithTitle:@"Checkbox"];
[checkBox setFrameOrigin:CPPointMake(40, 40)];
[contentView addSubview:checkBox];
var radioBox = [CPRadio radioWithTitle:@"Radio"];
[radioBox setFrameOrigin:CPPointMake(140, 40)];
[contentView addSubview:radioBox];
var disclosure = [CPDisclosureButton buttonWithTitle:@"Disclosure"];
[disclosure setFrameOrigin:CPPointMake(240,40)];
[contentView addSubview:disclosure];
var popUp = [CPPopUpButton buttonWithTitle:@"PopUp"];
[popUp setFrameOrigin:CPPointMake(340,40)];
[contentView addSubview:popUp];
This produces:
The checkbox and radio objects each had their own class methods unique to their classes.  They also had the same buttonWithTitle method which we used on the disclosure box and the pop-up box.  We can see from the Disclosure box, by the placement of the arrow image, that this box is not meant to have a title in the same way that other buttons do.  Similarly, the pop-up box has class methods, which suggest that this box needs more than just a title to behave properly. We will explore these things as we continue.

From here we will explore the controls separately.  We will continue to use the same project folder through these experiments:

CPControl: Buttons

This article is a closer look at text boxes as they relate to CPView objects.  Find other CPView articles, including additional articles on CPControl objects at the our root article on the CPView topic.

Buttons!  They are a critical point to every classic application.  Today's applications swap buttons with clickable tiles, and drag-n-drop interfaces, and intuitive auto-continuation triggers, but understanding how to create a button is a great way to implement basic functionality before you define those new age interfaces.

Create a New Project

If you have followed some of the other tutorials, you should be comfortable with creating new projects.  Create a new application folder for this tutorial.  I have called my project CPButton.

Just like the previous tutorials, we will avoid creating additional object files in order to explore the basic objects.  These tutorials are short, and the objects we create are short-lived, so we will work entirely within the AppController.j file.

First Steps

First, we will create the button the same way that we have created all our view objects up to this point; later we will look at a great feature of the CPButton class which removes the guess work about dimensioning.

Add the following code to your AppController, after the definition of the contentView variable:
var button = [[CPButton alloc] initWithFrame:CGRectMake(300,300,100,50)];
[contentView addSubview:button];
When you reload your page, you will see your new button, but it does not do anything, and it has no label.  You will also notice that it appears to be two buttons, but if you click on it, you will see that it responds as a single button.  We will fix the double button problem later.

Adding a Label

Buttons often need a label in order to have meaning.  We often make use of images, but for now, we will just add a simple text label.

To do this, we will use the method: setTitle.  Add the following code:
[button setTitle:@"My Button"]
Reloading the page will show:

Actions and Targets

The button does not do anything yet.  The question is, how do we invoke a method by pushing the button?  Also, which object is the method invoked on?  How do we get a variable pointer to that object?

Every CPControl object has the function setTarget: and setAction:.  These two functions allow us to call any object and method when a trigger event happens.  For buttons, the trigger event is the click action.  For text fields, the trigger event is pressing the Enter key.  Other controls have different trigger events.

Because we are not creating any unnecessary objects in these beginner tutorials, we will create our button and set the target as the AppController itself.  The action that we define tells the code which method of the target object to invoke.

To attach our button to the AppController object, we will add the following code within our AppController:
[button setTarget:self]
[button setAction:@selector(buttonMethod:)]
The first line of code here sets the object that is going to be invoked on the action event.  Within the AppController, "self" refers to the AppController instance.

The second line of code sets the method to be called on the action event.  We have named the method "buttonMethod".  This method does not exist yet; we will have to create it.

We could have put any method name in the @selector() call we choose.  For now, remember the following:

  • In the selector definition, always remember the colon after the method name.
  • In the selector definition, always remember that this is not a string and there are no quotes around the method name.
  • The method definition, at this point, should take only one argument of a generic object type; Cappuccino's generic object type is "id".
We will add the following method to our AppController:
-(void)buttonMethod:(id)aSender
{
     //put button code here.
     alert("Button has been pushed");
}
The name of the attribute is arbitrary.  Name it whatever you like.  When a CPControl object invokes the selector, by default, it passes the object that is invoking the action.  In this case, aSender would be a link to the button that initiated the call. This means that we can reuse the same method and point multiple buttons to it if we need to. I find that I seldom need to refer to the calling object in a button call, but this is an important lesson in how CPControl objects work in general, because there will be times when you will need to know that this object points to the object that invoked it.

Now that we have given the button a method, we can reload our project and push the button to find an alert window indicating success:

Creating a Button With Text: the Easy Way

We still have the matter of the double button appearance.  We can change the CGRect object and play guessing games with the dimensions to get the perfect appearance, but the Cappuccino developers made it much easier for us.  Remove the following lines of code from your application:
var button = [[CPButton alloc] initWithFrame:CGRectMake(300,300,100,50)];
[button setTitle:@"My Button"];
In their place, add the following:
var button = [CPButton buttonWithTitle:@"My New Button, which is the Perfect Size"];
[button setFrameOrigin:CPPointMake(300,300)]; 
 We find, when we reload the page, that our button still operates, as before, but now it is perfectly wide and tall to fit the label that has been created for it:

A Word On Images

The CPButton is a CPView object just like all the CPControls that we are discussing.  This means that, like all CPView objects, that it has sub-views.  We will not do so in this tutorial, but we could use addSubview to add image objects or any other view object to the button's display.  The power in Cappuccino's UI is knowing that everything derives from CPView, and therefore, everything behaves by the same view hierarchy rules.  Every CPView object can become its own container for subviews.

CPButton class adds a new method that CPControl does not have called setImage.  Images are so frequently used on our buttons, that the developers saw fit to make this easier for us.  Whether you use the sub-view features inherited from CPControl, or the setImage method of the CPButton class, you have options to play with.

The Limitations of CPButton

We saw, when we created our CGRect that was too tall, that the button uses an image that has a fixed height, and a variable width. We can use the +buttonWithTitle:(String) method to create a button that is perfectly sized, as long as it is only one line tall. If we add line breaks to the string, we find that our button does not change its dimensions.

If, at a later time, I discover a manner to change the manner in which this image is stretched. I will update this article.

Thursday, May 15, 2014

CPControl: Text Boxes, Labels, and Boxes.

This article is a closer look at text boxes as they relate to CPView objects.  Find other CPView articles, including additional articles on CPControl objects at the our root article on the CPView topic.

Text boxes and labels are one and the same in Cappuccino.  All labels are editable, if you set them to be.  The trouble is that our users don't instinctively know which fields we have set to edit and which fields have been set as static text.  In order to give a more classic look to your text boxes, you will need to utilize another control object to give the text box a border.

Create a new Project

If you have followed some of the other tutorials, you should be comfortable with creating new projects.  Create a new application folder for this tutorial.  I have called my project CPTextBox.

Just like the previous tutorials, we will avoid creating additional object files in order to explore the basic objects.  These tutorials are short, and the objects we create are short-lived, so we will work entirely within the AppController.j file.

Creating a Text Field

To the applicationDidFinishLaunching method, and after the definition of contentView, add the following code:
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(40,40,100,20)];
[textField setStringValue:@"New Text Field"];
[contentView addSubview:textField];
 The result of this new code is a simple label, much like the "Hello World" label in the center of the screen. We have not changed the font using the setFont:(CPFont)aFont method, so the font we see is the default font settings.  The label is not editable, because we have not set it to be.  The default setting to the isEditable attribute is NO.
A simple CPTextField object with default values.
To make the text field an editable input, we call setEditable:(BOOL)newValue.  Add the following line:
[textField setEditable:YES];
When you reload the page, nothing looks different. However, you will now be able to select and change the text. There is no field border, which makes the field difficult to find without placeholder text.

I have searched the CPTextField, CPControl, and CPView classes for a method that sets a border on the frame, but I have found nothing yet that will allow us to make a simple text box from this one object.  So, to make our text box, we turn to another class, which is responsible for making boxes.

The CPBox class is what we need to create the text box frame.  CPBox is a view, which means that we can put the text field inside the CPBox.  Let's create the box first, then we will move the text field into the box.

Create the box by adding the following code:
var textBox = [[CPBox alloc] initWithFrame:CGRectMake(150,40,110,25)];
[contentView addSubview:textBox];
This creates the following:

Now, we will move the textField.  Change the textField definition to the following:
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(5,5,100,20)];
We change the frame origin so that we can fit the text field in the box, which is only 25 units tall and 110 units wide.  Now, remove the following line:
[contentView addSubview:textField];
And add the following:
[textBox addSubview:textField];
Now, reload the page and look at what you have created:
 This has a much more comfortable feel, and if we never set the string value to @"NewText Field," our user still might be able to find our text box.  This text box has a much more natural feel to it, and all it really needs is a field label to tell the user what information to enter.  We won't cover creating the label, because this is just a simple matter of creating and positioning a new CPTextField that is not editable.

Extracting Values

Once the user has added text to the text field, we will ultimately need to pull the text out of the field in order to process it.  The simplest manner to do this is to allow the user to initiate a process by pressing a button.  We will not cover the Cappuccino equivalent to an onChange event in this tutorial.  Instead, add a button by copying and pasting the following text into your application.
var button = [CPButton buttonWithTitle:@"Do Something"];
[button setFrameOrigin:CGPointMake(300,300)];
[button setTarget:self];
[button setAction:@selector(doSomething:)];
[contentView addSubview:button];
textFieldAttribute = textField;
Then, add a new function to AppController:
-(void)doSomething:(id)notificationSender
{
     //we will put the button's code here
}
And change the attributes section of the AppController implementation definition to the following:
@implementation AppController : CPObject
{
     CPTextField textFieldAttribute;
}
 Reload the page, and you will see the following:
Clicking the button won't do anything yet.  But we now have a method for returning some program control to us after the page load.  We will use this to demonstrate retrieving values from the text box, and we will use a traditional javascript alert() function to display what we find, just to keep it simple.


Extracting a String Value

Change the doSomething: code to the following:
var StringValue = [textFieldAttribute stringValue];
alert( StringValue );
Reload the page, edit the text, and click the button. Then do it several more times.

You see from this example that it is as simple as calling "stringValue" to get the text value from any CPTextField.  We created the attribute textFieldAttribute, so that we could access the text field again after it is created.  We also were careful to save a handle to the CPTextField variable, textField, and not the variable textBox, which is a CPBox object, that contains the text field.

Extracting a Float Value

We can use traditional javascript to convert string values to numbers, but if we know in advance that we are expecting a numerical value, we can extract floating point values directly from the text field.  Change the doSomething: code to the following:
var NumberValue = [textFieldAttribute floatValue];
alert( NumberValue );
Reload the page, edit the text, and click the button. Then do it several more times.

Depending on how much you played with the values, you may have learned the following:
  • string values that contain no numerical values translate to 0.
  • string values that contain a numerical value are stripped of the string portion; only the floating point number value is returned.
  • if multiple numeric values appear within the text box, or a poorly formatted number like 12.1.3, only the first properly formatted floating point value is returned. (the example 12.1.3 returns 12.1).

Wednesday, May 14, 2014

Scrapbook as a View Hierarchy

Cappuccino Project asks us to make a scrapbook for our very first project.  They walk us through the steps of creating a view hierarchy, which is what we are currently reviewing as we look deeper at CPView objects.

Have you ever really stopped to look at the view hierarchy that we create?

The diagram below shows the view hierarchy that we create in that tutorial.  The view hierarchy is different than the object composition that we create.  We create several objects and store the layers and views as attributes of those objects, but the way that those objects are saved as attributes is different than how we stack them in the hierarchy.  For example, we create the variables _rootLayer, _paneLayer, and _borderLayer as attributes of the PageView class, but when we initialize the PageView object, we add the _paneLayer and the _borderLayer as subLayers of the _rootLayer variable.

The View Hierarchy of the Scrapbook Project. Diagram created in MagicDraw PE
This view hierarchy is comprised of both views and layers.  Together, they form a single tree, which composes our entire view in the Scrapbook program seen below.  This is the view hierarchy.

The visual depiction of the view hierarchy diagrammed above.

There are two view hierarchies in this diagram actually.  Each view hierarchy is rooted at a CPWindow object.  In this diagram, the two roots are both named "theWindow."  One root is a CPWindow, and this view fills the page and forms the display on which we see the image.  The other root is a CPPanel, and it floats on top of the other window in order to display the controls that we use to manipulate the image.

From each of these roots, every object seen in the tree is either a derivative of CPView or CALayer.

We are told that the difference between CPView and CALayer is that the CALayer has advanced graphic handling properties.  This implies that if we are not performing advanced graphic manipulations that we ought be able to create an entire application out of CPViews without any CALayers.  Every CPWindow is created with a CPView to display it's primary content, making it impossible to have a view hierarchy without this object.  However, we see in this tutorial that the CALayer requires the CPView in order to display itself; no layer is hosted from a CPWindow directly, so we might infer that CALayers are not necessary in any project.  This is why we study the CPView first.

CPView, the heart of Cappuccino UI

We have spent some time reviewing CPWindows, prior to this, because the CPWindow is the root node of every View hierarchy.  You can see from the inheritance tree excerpted from the Cappuccino Project's online documentation, that almost every useable element of a page is derived from this central element, and as CPView objects, each can form a branch or leaf on the CPView tree which forms all that we interact with.
CPView Inheritance Diagram: excerpted from Cappuccino-Project's Online Documentation
Once one has a basic understanding of the CPWindow object which starts a view tree, it is important to carefully study the view objects and how to combine them to form interactive and engaging applications that are comfortable to use and powerful in those things the application can accomplish.

We will take some time over the next week to look deeper into Views.  We will consider labels, text boxes, buttons, and eventually consider even more of these items depicted above.

Tutorials

Tuesday, May 13, 2014

Playing with Additional Window Features

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.

Project Files

For this project, use the files you created from the Multiple Windows tutorial.  Copy them to a new directory.

Playing with Windows

I am a big proponent of play.  By making small changes to code and experimenting, we learn what is available to us in the Frameworks created by others.  In this tutorial, I will show you some of the CPWindow methods that are in the documentation, and I will encourage you to just play with them.

Alpha Values and Transparancy

CPWindows have a function called setAlphaValue.  Try setting this value to ranges between 0 and 1 on any of our windows.  I have added the following lines of code to the original AppController.j file:
    [theWindow setAlphaValue:0.5];
    [theWindow2 setAlphaValue:0.2];
This resulted in the following effect:


 Background Color [Not working as expected]

CPWindows have a function called setBackgroundColor:.  I have added the following lines of code to the original AppController.j file:
    [theWindow setBackgroundColor:[CPColor blueColor]];
    [theWindow2 setBackgroundColor:[CPColor redColor]];
This produced unexpected results.  One would expect this to change the color of the window's background.  Instead, no effect is seen:

Setting Minimum and Maximum Sizes

CPWindows have setMinSize: and setMaxSize: functions.  They take CPSize objects as the argument.  I added the following lines of code to my AppController.j file:
    [theWindow setMinSize:CPSizeMake(100,100)];
    [theWindow2 setMinSize:CPSizeMake(150,150)];
As a result, I am unable to resize my windows to dimensions smaller than these indicated sizes as shown here.  We can also control the resizing of the window with a maximum size limit using the setMaxSize: method.

Removing the Window's Shadow - Making a Flat Window

CPWindows allows one to choose whether the shadow exists or not with setHasShadow:(Bool).  Remember, Cappuccino does not use true and false like most languages; this language uses YES and NO.  I added the following line of code to my AppController.j file:
    [theWindow setHasShadow:NO];
This produced the following:

Autoresizing

Cappuccino Project does a really good job explaining the resizing masks, so I will defer to their article for information about auto-resizing:  Cappuccino Project's Tutorial on Autoresizing

Other Functions

There are many other methods which we may address in the future.  By all means, read through the CPWindow.j file and play with the methods that you find there.  Playing is the only way to truly learn how to work with these objects.




Using a Window Controller

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.

Starting From A Previous Project

For this short Window Controller project, we will use the files that you during the Multiple CPWindow Tutorial.  Copy these files to a new directory called "WindowController."

Window Controllers

In the Photo Album tutorial, we are told that Window Controllers control CPWindows, but we have seen that we can manipulate and control CPWindows on our own.

Window Controllers do manipulate CPWindows with a more finesse.  For example, when calling orderFront on the CPWindow, the Window Controller also performs additional checks to determine if the window needs to be marked as the "key" window as well.  The "key" window is the target of keyboard entries.

Converting the Multiple Window Project

We are going to make three simple changes to the Multiple Window project files that you copied.

First, we are going to change the attributes of the AppController object from their current object type to CPWindowController.  Change all three of them.  Even the CPPanel can be controlled by a CPWindowController, because, as we should remember, the CPPanel is a child of CPWindow.

The implementation now looks like this:
@implementation AppController : CPObject
{
        CPWindowController Window1;
        CPWindowController Window2;
        CPWindowController thePanel;
}

Second, we change the assignment of these variables to hold Window Controller objects.  Previously, we assigned the CPWindow objects directly to the variables.  We will still use those CPWindow objects, but now we will pass them to the initWithWindow:(CPWindow)aWindow method of the CPWindowController as we initialize our objects.

The assignment lines should now read:
    Window1 = [[CPWindowController alloc] initWithWindow:theWindow];
    Window2 = [[CPWindowController alloc] initWithWindow:theWindow2];
    thePanel = [[CPWindowController alloc] initWithWindow:theWindow3]; 
 Notice that the CPPanel (theWindow3) is being passed as a CPWindow.  This is because CPPanels are CPWindows.  This is a benefit of the inheritance relationship.

Lastly, we change the functions that were called by the buttons.  CPWindowControllers don't have a visual display, and they therefore don't have an orderFront method.  Instead, Window Controllers have a "showWindow" method.  Like the CPWindow's orderFront: method, we need to pass an object to this method, but the object is truly irrelevant, so we can also pass a nil object.  We should pass the object that is requesting that the window is displayed, which is why it is very common to see "showWindow:self" or "orderFront:self".

The button methods now should look like this:
-(void)showWindow1:(id)aSender
{
    [Window1 showWindow:self];
}
-(void)showWindow2:(id)aSender
{
    [Window2 showWindow:self];
}
-(void)showPanel:(id)aSender
{
    [thePanel showWindow:self];
}
When we run the new application, it looks exactly like the Multiple Windows tutorial.  However, this code now uses Window Controllers to properly handle the CPWindow objects.



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.


Playing With CPWindow Style

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 Windows

The very first window that the Photo Album Tutorial has us create is a "Borderless Bridge Window".  The Bridge is the connection between your cappuccino project and the user; in other words, the Bridge is the browser itself.  A Borderless Bridge Window appears to be a blank page that expands to fill the entire browser window.  Later, we will see that we can use this "Borderless Bridge" concept to expand the window to fill any space, including other views or sub-views.

This Borderless Bridge Window, however does not give one the opportunity to really appreciate what the window is providing, so we are going to play with the concept a bit.  Open the AppController.j file in our WindowStyle project using your favorite text or code editor.  I use Vim, because it is universally available on all linux or unix based systems like Mac OS X.

You will see that the variable theWindow is set to a CPWindow object which is already set to the "Borderless Bridge Window" view style.  What happens if we play with that setting?

Using the Cappuccino API Documentation, we can find the CPWindow object, and if we look carefully at the files that are available in the left panel, we see that there is a separate file that is used to define the CPWindow constants.  Looking through this file for any constant that has the word "WindowMask", we find a list of all the available window styles that are available to us: 
One at a time, we will try each one by replacing the CPBorderlessBridgeWindowMask string on the "theWindow" variable's object definition.  Before we do that, however, it will be important for us to make some changes to the code.  First, change the CPBorderlessBridgeWindowMask to CPBorderlessWindowMask, and reload your project.

What happened to "Hello World"?!
Did the lack of the Borderless Bridge definition cause the window to fail to make a connection to the "bridge" (a.k.a. browser window)?  Does the Borderless Window require another CPView object in order to display?

Not so much.  The Borderless Bridge Window Mask overrides all size definitions, which is why theWindow is initialized with a CGRectMakeZero() content area.  The CGRectMakeZero() function returns a content rectangle that is zero units tall and zero units wide located at the origin.  The Borderless Bridge Window Mask, says regardless of what the defined area is, stretch it to fill the entire browser window ("the bridge") and fix the top left corner at the top left corner of the browser window.

If we change the CGRectMakeZero() to CGRectMake( 20, 40, 500, 300 ), we will now see our Borderless Window Mask does display data.  Unlike the Borderless Bridge Window Mask, the Borderless Window Mask must have a window size and location definition.  You will find that most of the windows require a CGRect definition, so I recommend defining our window with CGRectMake( xpos, ypos, width, height), and then defining the variables xpos, ypos, width and height with values of your choosing on separate lines.

After making adjustments, my code looks like this:

var xpos = 20;
var ypos = 40;
var width = 500;
var height = 300;
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(xpos,ypos,width,height)
      styleMask:
      CPBorderlessWindowMask
      ],
      contentView = [theWindow contentView];

We have done two things here:  

1) we have moved CPBorderlessBridgeWindowMask to its own line.  I find it easier to play when I have easy access to the code that I am playing with.

2) we replaced CGRectMakeZero with CGRectMake(xpos,ypos,width,height).  Feel free to define your own xpos, ypos, width and height.

Now, you can safely try each window mask and see how each Window Mask affects the display.  I will make notes here on what I find as I play, and hopefully we will find some of the same observations:

CPBorderlessWindowMask


The Borderless Window Mask removes all edges from the window and fits the window to the CGRect object that we specify.  It looks as if the content is off center, but in actuality, we have told the system that we only want to use part of the screen.  The "Chrome" is missing which defines the edges of the window.  We will see this "chrome" come back as we try other masks.

CPClosableWindowMask

The Closeable Window Mask gives me a visible window and a title bar with a functional "close" button.

The Closable Window Mask restores the chrome.  Now the window appears to float over an empty background, and our "Hello World" text no longer looks off-center, as it did when we could not see the border of our window.  The Closable Window Mask added a title-bar which holds a functional closing button.  If you click the button, your window disappears.  Don't worry about that for now, we will learn how to get the window to re-open again in a future tutorial.  For now, it is important to note that the Closeable Window Mask gives us that close button, and we do not need to worry about adding any functionality, Cappuccino does all that for us.

Another thing you may notice, if you have a keen eye, is that the space between the top of the browser's content area and the top of the window appears to be much smaller than the space between the left side of our window and the left edge of the browser's content area.  We defined the location to be 20 units left and 40 units down, so the window should be appear with a larger gap at the top; shouldn't it?

What we see here is actually addressed in the Photo Album tutorial, only briefly.  The content rectangle defines the content area of our window, the "Chrome" frames the content area.  We will find that the content area of our window is positioned at (20,40), and the title bar and shadows are outside the rectangle defined by the 500 unit wide and 300 unit tall area that we specified.  This is part of the reason that I defined the top edge at (20,40).  If I defined it at (0,0) or even (20,20) your title bars would all be cut off and not visible (at least partially).  Keep this in mind as you lay out your pages: if you don't allow space for your title bar, it will not display.

If you are feeling adventurous, try to drag and drop the window.  You will not be able to move it by clicking on the content area, but if you click on the title bar, you will be able to move the window to any location on the browser that you desire.  This is a default behavior of all windows in Cappuccino.

By clicking the title bar, I am able to drag the window to new locations.

CPHUDBackgroundWindowMask

Look closely, and you will see "Hello World" is still present.  The HUD Background is dark and often requires changes to font color for readability.

The HUD Background still has a titlebar and text, although it is very difficult for us to see.  This background is dark!  We can still drag and drop the window if we grab the window where the title bar would be, and the text can be barely seen in the center of the window.  If you have trouble seeing the text, try selecting it with your mouse, and you might see it more clearly.  The HUD Background can be a very cool look, but remember that you will have to change the font colors in order to help your user read the content.

CPMiniaturizableWindowMask


If the Closeable Window Mask gave us a close button, then the Miniaturizable Window mask must add a button that lets us shrink the window and tuck it away on an application bar somewhere.  Perhaps this is the intended functionality, but as you can see, unlike the Close Mask, the Miniaturizable Window mask does not add this functionality right out of the box.  Perhaps later we will see how to use this mask in a way that provides some cool benefit, but for now, we see that it produces a plane window with at title bar and no buttons.

CPResizableWindowMask

Resizable Window Mask lets the user change the window dimensions on the fly.

Resizable Window Mask lets the user change the window dimensions on the fly.

Up till now, if you have tried adjusting the size of the window, you have found that you are out of luck.  The Resizable Window Mask changes everything.  Now, you can change the window dimensions by dragging the bottom or right edges.

CPTexturedBackgroundWindowMask

Textured Background Window Mask is the default mask for most non-specified window masks.
What happened?  This window looks much like several of the other windows we have seen so far.

Later, I will show you how Masks can be combined.  What we are actually seeing here is the result of what happens when not enough masks have been provided to define the chrome.  Cappuccino uses the "Textured Window Mask" as a default window "chrome" whenever the user fails to specify the look of the window.  This is why when we defined that we wanted a closable window, we got a window that had the same general appearance as this.  Cappuccino said, "Okay, you want the window to be closable, but you didn't specify how it should look, so I'll give you a Textured Window."

The "texture" is a flat-color which is off-white and sets the window apart from the rest of the unused, white page.  The textured window has a title bar by default, which is used as a handle to move the window around the page, and it cannot be resized unless the resizable mask is specified.

CPTitledWindowMask

The Titled Window Mask accents the title bar by making it just a little darker for a Textured Background Window.
To demonstrate the Titled Window Mask, I jumped ahead a bit and added another window with just the "CPTexturedBackgroundWindowMask".  You will see that the titled window mask makes the title bar just a little darker than it would otherwise be.  The effect is negligible but present.

This mask does not have any effect on the HUD Background, and overrides the Borderless Window to have a Textured Background.

Combining Masks In Cappuccino

If you are not familiar with the concept of "masks," you should take a moment to read the blog article on "How Masks Work."  If you already have a basic understanding of masks, then this next part should flow naturally.

Cappuccino uses masks to store your requests about how you want your Window to look in a very compact bit string.  As a result, you are able to combine any Masks that you feel fit your need into a single request.  We combine masks by creating an equation that lists all the masks we want combined by a bitwise-OR operator ("|").

In practice, we do the combination and pass the result of the calculation within the object definition like this:
  var newWindow = [[CPWindow alloc] initWithContentRect:CGRectMake( 25, 25, 100, 200) styleMask:CPTexturedBackgroundWindowMask | CPClosableWindowMask | CPResizableWindowMask];

However, we can just as easily define the style mask separately.

var __CustomClosableResizableTexturedWindow = CPTexturedBackgroundWindowMask | CPClosableWindowMask | CPResizableWindowMask; 
var newWindow = [[CPWindow alloc] initWithContentRect:CGRectMake( 25, 25, 100, 200) styleMask:__CustomClosableResizableTexturedWindow];
When the styleMask you specify lacks information that is necessary, Cappuccino assumes default values for the missing components.  This is why your CPClosableWindowMask looks the same as the a CPClosableWindowMask | CPTexturedBackgroundWindowMask.  The CPTexturedBackgroundWindowMask is assumed by Cappuccino when no visible chrome style is specified.

In order to see what is possible, just play with different combinations of style masks to see what you can create.  Sometimes, Cappuccino will override your request, because the designers have determined your request is not practical. For example, if you try (CPClosableWindowMask | CPBorderlessWindowMask); Cappuccino will override your request and give you (CPClosableWindowMask | CPTexturedBackgroundWindowMask), because the borderless window has no shape or definition on which to place the close button.

---------------
This concludes the Playing with CPWindow Style practice session.  Go back to the CPWindow overview article to find other fun, interactive, informative lessons about Cappuccino.