Tutorial: A Model-View-Controller in Processing
--D. Thiebaut (talk) 10:30, 26 November 2014 (EST)
This tutorial presents a simple Model-View-Controller implemented in Java, using Processing. The viewer is the Applet, and separate classes define the model and the controller. The system displays a simple grid network of vertices connected via random edges to their four neighbors (up, down, left, right).
Requirements
- You need to setup Eclipse to run Processing apps. This is simply accomplished by
- importing the core.jar file found in a new installed Processing 2 application on your system (Windows or Mac) into your Eclipse project.
- Control/Right clicking on core.jar in Eclipse, selecting Build Path, then Add to Build Path.
- You can find more detailed instructions here or on the Web.
Source
Controller
import processing.core.PApplet;
/**
* MVC1_controller.
* The controller of a simple Model-View-Controller (MVC) system.
* The MVC system displays a 50x50 network of vertices with left,
* right, up, and down edges to their neighbors.
* The controller is started by the viewer, which extends PApplet,
* and which is the first object to become alive.
* The controller starts the model which initializes the graph.
* Every graphical interaction in the viewer is passed on to the
* controller.
*
* @author D. Thiebaut
*
*/
public class MVC1_controller {
// links to the viewer and model.
MVC1_viewer viewer = null;
MVC1_model model = null;
/**
* constructor.
*/
public MVC1_controller() {
}
/**
* mutator
* @param v reference to the viewer
*/
public void setViewer( MVC1_viewer v ) {
viewer = v;
}
/**
* mutator
* @param m reference to the model
*/
public void setModel( MVC1_model m ) {
model = m;
}
/**
* launched by the viewer, which passes a reference to itself in the
* process. Creates a set of references between the model, the view,
* and the controller.
* Initializes the graph held by the model (vertices and edges)
* @param v reference to the viewer.
*/
public void initAll( MVC1_viewer v ) {
// init viewer
viewer = v;
// create the model
model = new MVC1_model();
// create reference system between MVC components
model.setController( this );
viewer.setModel( model );
model.setViewer( viewer );
// create the graph of vertices and edges
model.initGraph();
}
/**
* called by viewer when the mouse is over a given vertex.
* decides what viewer should do, depending on different
* modes of operation.
* @param vertex
*/
public void vertexUnderMouse( int vertex ) {
viewer.displayVertexName( vertex );
}
}
Viewer
import java.util.ArrayList;
import processing.core.PApplet;
/**
* The viewer class of a simple Model-View-Controller (MVC) system.
* The MVC system displays a 50x50 network of vertices with left,
* right, up, and down edges to their neighbors.
* This class is the main class, as it extends PApplet, which will
* automatically call setup() on start-up, and will repeatedly call
* draw(), 30 times a second.
* Constantly redraws the network of vertices and edges held by the
* model, 30 times a second.
* Passes special conditions to the controller for decision. For
* example, when the mouse is over a vertex, or when the mouse
* is clicked somewhere on the canvas of the applet.
*
* @author D. Thiebaut
*
*/
public class MVC1_viewer extends PApplet {
MVC1_controller controller = null;
MVC1_model model = null;
public int WIDTH = 800; // best generic width
public int HEIGHT = 800; // best generic height
private int DELTAX = 10; // no pixels between vertices
private int DELTAY = 10; // no pixels between vertices
private int MINDIST = 5; // min dist for vertex detected
// as under mouse pointer
/**
* mutator: sets a reference to the model.
* @param m reference to the model
*/
public void setModel( MVC1_model m ) {
model = m;
}
/**
* helper function. Given a vertex number, returns its position
* (x, y) on applet canvas. Vertex 0 will be at (DELTAX, DELTAY).
* @param vertex the vertex to position
* @param noRows the number of rows in the network
* @param noCols the number of columns in the network
* @return an int array. xy[0] is x, xy[1] is y. In pixels.
*/
private int[] getXY( int vertex, int noRows, int noCols ) {
int[] xy = new int[2];
xy[0] = DELTAX + (int) ( vertex / noCols ) * DELTAX;
xy[1] = DELTAY + ( vertex % noRows ) * DELTAY;
return xy;
}
/**
* the entry point for the MVC system. The PApplet will automatically
* call setup() first. This is the place that initializes everything
* up, including controller and model. Gets the controller to link
* the 3 systems together. The controller will initialize the model with
* the graph.
*/
public void setup() {
//--- create model and controller, create reference ---
//--- system between all three. ---
controller = new MVC1_controller( );
controller.initAll( this );
//--- make window fit model graph exactly ---
//--- add blank area of 30 pixels high at bottom ---
DELTAX = round( WIDTH / (model.getNoCols() + 1 ) );
DELTAY = round( HEIGHT / (model.getNoRows() + 1 ) );
WIDTH = DELTAX * (model.getNoCols() + 1 );
HEIGHT = 30 + DELTAY * (model.getNoRows() + 1 );
//--- set Applet geometry ---
size( WIDTH, HEIGHT );
smooth();
}
/**
* draw() is called automatically 30 times a second by mechanism inside
* the PApplet that is hidden from the programmer. The role of draw() is
* to erase the screen (too fast for the user to notice) and redraw the
* graph with vertices and edges, and whatever text needs to be displayed
* on the canvas.
* Detects if the mouse pointer is over a vertex and if so, calls the
* controller to decide what should be done in this case.
*/
public void draw() {
// erase window, make background white
background( 255, 255, 255 );
// display network of vertices
int V = model.getNoVertices();
int noRows = model.getNoRows();
int noCols = model.getNoCols();
// draw the vertices and edges
fill( 0, 0, 0 ); // make vertices black
stroke( 2 ); // make line width 2 pixels
for ( int v = 0; v < V; v++ ) {
int[] xy = getXY( v, noRows, noCols );
ellipse( xy[0], xy[1], 4, 4 );
// get adjacency list for each vertex
for ( int u: model.adjList( v ) ) {
int[] xy2 = getXY( u, noRows, noCols );
line( xy[0], xy[1], xy2[0], xy2[1] );
}
// detect if vertex is under mouse pointer
if ( dist( xy[0], xy[1], mouseX, mouseY ) < MINDIST )
controller.vertexUnderMouse( v );
}
}
/**
* displays the vertex number next to the circle representing it.
* This is typically done whenever the mouse pointer is over a vertex.
* @param vertexNo
*/
public void displayVertexName(int vertexNo ) {
// get position of vertex
int[] xy = getXY( vertexNo, model.getNoRows(), model.getNoCols() );
// set text color to red
fill( 255, 0, 0 );
// display text
text( " "+vertexNo, xy[0], xy[1] );
}
}
Model
import java.util.ArrayList;
import java.util.Random;
/**
* Model component of Model-View-Controller system.
* Implements the graph, holds the vertices (numbered 0 to maxNoVertices)
* and keeps the edges in a boolean adjacency matrix, that is
* maxNoVertices x maxNoVertices;
* @author D. Thiebaut
*
*/
public class MVC1_model {
private int maxNoVerticesRows = 50; // geometry
private int maxNoVerticesCols = 50; // 50 x 50
private int maxNoVertices = maxNoVerticesRows * maxNoVerticesCols;
private boolean[][] adjMat = null; // adjacency matrix
private int probability100 = 65; // 65% change of edge
private MVC1_viewer viewer = null;
private MVC1_controller controller = null;
/**
* constructor. Nothing to do. The building of the network is done
* by initGraph().
*/
public MVC1_model( ) {
}
/**
* mutator. Sets the reference to the controller.
* @param c
*/
public void setController(MVC1_controller c) {
controller = c;
}
/**
* mutator. Sets the reference to the viewer.
* @param v
*/
public void setViewer( MVC1_viewer v ) {
viewer = v;
}
/**
* accessor
* @return number of vertices in graph
*/
public int getNoVertices() {
return maxNoVertices;
}
public int getNoCols() {
return maxNoVerticesCols;
}
public int getNoRows() {
return maxNoVerticesRows;
}
/**
* add an undirected edge between u and v.
* @param v
* @param u
*/
public void addEdge( int v, int u ) {
adjMat[v][u] = true;
adjMat[u][v] = true;
}
/**
* generate a random graph. The seed of the random number generator
* is set to a fixed number, so that the graph generated is always the same.
*/
public void initGraph() {
Random random = new Random();
random.setSeed( 12345 ); // seed for random number generator
adjMat = new boolean[maxNoVertices][maxNoVertices];
for ( int r=1; r<maxNoVerticesRows; r++ ) {
for ( int c=1; c<maxNoVerticesCols; c++ ) {
int currentVertex = r * maxNoVerticesCols + c;
int upVertex = (r-1) * maxNoVerticesCols + c;
int leftVertex = r * maxNoVerticesCols + c - 1;
// should we create an up connection?
if ( random.nextInt(100) <= probability100 )
addEdge( currentVertex, upVertex );
// should we create a left connection?
if ( random.nextInt(100) <= probability100 )
addEdge( currentVertex, leftVertex );
}
}
}
public ArrayList<Integer> adjList( int v ) {
ArrayList<Integer> adj = new ArrayList<Integer>();
for ( int u=0; u<maxNoVertices; u++ )
if ( adjMat[v][u] )
adj.add( u );
return adj;
}
}