«Demo of Model-View-Controller design» by jamshark70

on 09 Dec'18 02:49 in guiviewcontrollermvcmodel

This question comes up often: What is the best way to structure GUI code.

IMO, Model-View-Controller (when done correctly) gives you the most flexibility while avoiding common bugs. But it's hard to wrap your head around it first. So, here's a quick example using NodeProxies.

  • Model: This is the thing that is actually doing the work. Here, it's a control proxy representing a frequency value. When something interesting happens in the model (e.g., the frequency changes), it should broadcast a notification (in SC, using the '.changed' method). NodeProxy already does this internally (which is why I'm using it for this example!).

  • View: The GUI display -- a Slider, in this case. The view knows who its model is, and should respond to user action by updating the model.

  • Controller: An object that receives notifications from the model and communicates with the view. This SimpleController object is looking for '\source' notifications, and changes the view's displayed value.

Benefits: You can make as many views as you want, on the same model, and their separate controllers all receive the notifications and keep everything in sync without having to manage exponentially-increasing connections. You can set the model programmatically and all views automatically update.

Drawbacks: The model needs to send notifications. Sometimes it's hard to anticipate which notifications will be needed. Bugs can be hard to track down (but when each component is doing its job correctly, it ends up being easier to maintain).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
s.boot;

(
~model = NodeProxy.control(s, 1).source_(440);

~makeView = { |model|
	var view = Slider(nil, Rect(800, 200, 200, 40))
	// user action: change the model
	.action_({ |view|
		model.source = \freq.asSpec.map(view.value);
	})
	.value_(\freq.asSpec.unmap(model.source)),
	
	// 'args' depends on who is sending the notification
	// NodeProxy sends an array, where the new 'source' is args[0]
	controller = SimpleController(model).put(\source, { |obj, what, args|
		defer { view.value = \freq.asSpec.unmap(args[0]) };
	});
	
	view.onClose = { controller.remove };
	
	view.front
};

~sound = NodeProxy.audio(s, 2).source_({
	SinOsc.ar(~model.kr(1), 0, 0.1).dup
}).play;
)

a = ~makeView.(~model);
b = ~makeView.(~model);  // 2nd view-controller, auto-synced!

~model.source = 220;

~model.dependants  // 2 controllers

// close windows

~model.dependants  // empty

~model.clear; ~sound.clear;
raw 937 chars (focus & ctrl+a+c to copy)
reception
comments
Klangschmied user 09 Dec'18 09:00

Thanks, the approach is the same as "Jamoma-package" in Max (MVC). The last line give me errors...