Models of Views

Model-View-Controller is a common object-oriented pattern. You will find it in every well-written GUI application you ever see, and any reasonable web-based application. And application in these spaces that fails to implement Model-View-Controller is probably going to be an unmanageable mess, impossible to extend reliably, and something you should consider walking away from or rewriting.

In this laboratory, you'll take a simple application written in the model-view-controller style, and extend it with three new views and two new controls. As a result, you'll see how to do object-oriented, GUI programming using PLT Scheme.

Both images used to denote this lab are taken using a style of photography called tilt-shift photography. Above, a photo by northcountryboy, may have used the technique, or it may have been faked using Photoshop. That does not mean it was a hand-drawn image—instead, I mean that he may have faked the actual use of tilt-shift photography, and instead just used Photoshop to achieve the same effect. Regardless, it's a view of things that makes them look like models...

Use the Source

20090312-mvcThe first thing you and your partner should do is download the starter file for this laboratory. Discuss it; in fact, go line-by-line, and figure out what the code does. Good pair programming practice, as you recall, requires a great deal of communication. If you're unclear on something, ask a colleague or myself.

You should, naturally, run it as well. First, switch DrScheme into the Module language, and then load and Run the source. The controller is the frame containing the slider, and the view is the color swatch. I have separated them into two separate frames so that you can easily see the distinction.

Experimentation

Start simple.

  • If you want, remove the text-view. It actually slows things down.
  • In the function main, create multiple copies of the gui-view. This involves adding a new let-binding for each new object, and then adding each object as an observer to the model.
  • Change the labels of the two frames. The frames are defined as part of the controller% and view% classes.
  • Change the size of the frames. You should be able to change both their initial size as well as their minimum size. 
  • Change the range that the slider operates on. Instead of going from black to red, limit the range to something that goes from half- to full-intensity.
  • Change the color of the swatch. For example, have it explore the space of the color green, or blue, instead of red.


The purpose of this exploration is to reinforce the reading you and your partner did, and lets you see how the different parts of the code work.

The Project

When you are done, you want your program to look like this:

20090312-mvc2

There should be three sliders and three color swatches. I have marked places that you probably need to edit your code with a comment labeled EXTEND.

Update the Model

The model currently only has one field: red-value.

  1. Add two more fields: a green-value and a blue-value.
  2. Add two more setter methods: set-green-value! and set-blue-value!
  3. Add two more getter methods: get-green-value and get-blue-value


 Remember, the model is just the data. Therefore, it should only be a collection of fields and methods to modify/retrieve the value of those fields. Nothing relating to a GUI should end up in the model.

Don't forget: when you change a value in the model, you need to send an update message to everyone who is an observer! (Have you figured out how that is done yet? It's there in the code, and in particular, defined in the parent class of the model...)

You should still be able to run your code at this point. Even though you've changed the model, nothing should have changed in the GUI or controller; therefore, it will be the same, one-color-swatch program.

Update the View

I made my view 600 pixels wide, so as to accommodate the color swatches. You may want to do the same. (Technically, hard-coding these values is very bad practice. If we ever wanted to update this program, it would be a mess of number-tweaking.)

To add another color swatch, you need to do four things:

  • Fetch the value of the color you are creating a swatch for. I do this in a let for the red-value. Perhaps you'll call your variable... green-value?
  • Create a new brush object (documentation). In Photoshop, when you use the Paint Bucket tool, you have to choose a color. The brush, in this context, plays a similar role. We're defining what color and stroke will be used to draw on the screen. Define a new brush that will allow us to create a green swatch of color.
  • A device context gives us an abstraction for the screen (see the documentation). We draw on the device context. The reason for this abstraction is that the device context might actually represent an image, or a PDF, or perhaps even printer output. But, in all these cases, we just want to draw something. Send the dc a message telling it to use the green brush.
  • Immediately after you set the brush of the device context, you can draw a new color swatch. I'm using rounded rectangles. See the documentation for drawing rounded rectangles to figure out which of those numbers mean what.


If you use the code in the view as your guide, you should be able to repeat these steps for both a green color swatch and a blue color swatch. I did it through some copy-paste and a bit of editing, but then, I'm familiar with the code.

(There are more elegant ways to do this... we're not going for elegance today.)

When you get done with this step, you should have a GUI that has three color swatches side-by-side. But, you will still only have the ability to control one of the colors.

Update the Controller

In most GUI frameworks, the result of fiddling with a widget (pushing a button, moving a slider, etc.) generates an event. These events are either handled by a listener (in the Java model) or a callback (in a number of other languages). A callback, so you know, is nothing more than a function that is called when the widget is fiddled with.

For example, I have written function called red-callback.

(define (red-callback slider-widget an-event)
      (let ([slider-value (send slider-widget get-value)])
        (send the-model set-red-value! slider-value)
        ))


My callback takes two arguments: the slider object itself, and the event object.   Sometimes, we need data contained within the event object; this time, we do not. The slider object, however, has a lot of useful information (documentation). Among other things, we can get the value of the slider by invoking its get-value method.

So, the red-callback gets the value of the slider, and then updates the model using that information. That is ALL it does. 

To update the controller, you need to do two things:

  1. Create two new callbacks called green-callback and blue-callback. These should call the appropriate setters in the model.
  2. Create two new slider objects, and attach them to the frame. They will be very, very similar to the red slider, but you should attach the appropriate callback function to each one. That is, the green slider should use the green-callback function, and so on.


Your code should be executable at this point, and it should be done! I'd like to see your application functioning before you head out the door.

Next Steps

One thing you could do is do a Save As and modify your program so it has three color sliders and only one color swatch. Then, your sliders could control the red, green, and blue values of that swatch simultaneously. 

Another possible extension would be to eliminate all of the repetition in your code: is there a better way to build the GUI and callbacks, so we don't have all of this copy-paste work taking place?

Lastly, you could explore how GUIs are put together more generally. Throughout this lab, I've linked to the PLT Scheme documentation for the GUI framework (called MrEd). Perhaps you might want to go on and explore the GUI framework further. The PLT Scheme GUI framework is built upon the open-source wxWindows framework, and runs equally well on Linux, Mac, and Windows. I have often created small applications that I can then compile, distribute, and run on all of these platforms using PLT Scheme.

For example, the simplest code I can write to put a GUI frame on the screen is as follows:

#lang scheme/gui
(define f (new frame%
               (label "Uber")
               (min-width 300)
               (min-height 200)))
(send f show true)


This creates a new frame% object, and then I ask that object to show itself. Easy-peasy.

Or, you might go look at other object-oriented frameworks, and see how you would implement this application in your favorite language. The concept of model-view-controller should be the same: it is just a question of how much work is necessary to get everything tied together.